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
export default defineConfig([
globalIgnores(['dist']),
globalIgnores(["dist"]),
{
files: ['**/*.{ts,tsx}'],
files: ["**/*.{ts,tsx}"],
extends: [
// Other configs...
@ -34,40 +34,40 @@ export default defineConfig([
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname,
},
// 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:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
import reactX from "eslint-plugin-react-x";
import reactDom from "eslint-plugin-react-dom";
export default defineConfig([
globalIgnores(['dist']),
globalIgnores(["dist"]),
{
files: ['**/*.{ts,tsx}'],
files: ["**/*.{ts,tsx}"],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
reactX.configs["recommended-typescript"],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
]);
```

View file

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

View file

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

View file

@ -1,252 +1,273 @@
import './App.css'
import "./App.css";
import { useAuth, hasAuthParams } from "react-oidc-context";
import React, {useState} from 'react';
import React, { useState } from "react";
import {
SettingOutlined,
UserOutlined,
DeploymentUnitOutlined,
} from '@ant-design/icons';
import {Breadcrumb, type MenuProps} from 'antd';
import { Layout, Menu, theme } from 'antd';
SettingOutlined,
UserOutlined,
DeploymentUnitOutlined,
} from "@ant-design/icons";
import { Breadcrumb, type MenuProps } from "antd";
import { Layout, Menu, theme } from "antd";
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 {type UserContextType} from './hooks/UserContext.ts';
import {UserContext} from './hooks/UserContext.ts';
import type {userObject} from "./hooks/UserContext.ts";
import { type UserContextType } from "./hooks/UserContext.ts";
import { UserContext } from "./hooks/UserContext.ts";
import type { userObject } from "./hooks/UserContext.ts";
import Organisations from "./Organisations.tsx";
import {RefreshContext} from "./hooks/RefreshContext.ts";
import { RefreshContext } from "./hooks/RefreshContext.ts";
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";
const { Header, Content, Sider } = Layout;
type MenuItem = Required<MenuProps>['items'][number];
type MenuItem = Required<MenuProps>["items"][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
return {
key,
icon,
children,
label,
} as MenuItem;
}
const App: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const {
token: {colorBgContainer, borderRadiusLG},
} = theme.useToken();
/*******************
const navigate = useNavigate();
const location = useLocation();
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
/*******************
SIGNING IN WITH OIDC
*******************/
const auth = useAuth();
const hasTriedSignin = React.useRef(false);
// automatically sign-in
React.useEffect(() => {
if (!hasAuthParams() &&
!auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading &&
!hasTriedSignin.current
) {
auth.signinRedirect();
hasTriedSignin.current = true;
}
}, [auth]);
const defaultUser: UserContextType = {
currentUser: {
first_name: "",
last_name: "",
email: "",
organisations: [],
id: -1
},
setCurrentUser: () => {}
const auth = useAuth();
const hasTriedSignin = React.useRef(false);
// automatically sign-in
React.useEffect(() => {
if (
!hasAuthParams() &&
!auth.isAuthenticated &&
!auth.activeNavigator &&
!auth.isLoading &&
!hasTriedSignin.current
) {
auth.signinRedirect();
hasTriedSignin.current = true;
}
}, [auth]);
const defaultOrg: OrgContextType = {
currentOrg: {
name: "",
status: "partial",
root_user_email: "",
billing_contact: {email: "", id: -1},
owner_contact: {email: "", id: -1},
security_contact: {email: "", id: -1},
organisation_id: -1,
intake_questionnaire: {
question_one: "",
question_two: "",
question_three: "",
},
},
setCurrentOrg: () => {}
}
const defaultUser: UserContextType = {
currentUser: {
first_name: "",
last_name: "",
email: "",
organisations: [],
id: -1,
},
setCurrentUser: () => {},
};
const [currentUser, setCurrentUser] = useState<userObject>(defaultUser.currentUser);
const [refreshKey, setRefreshKey] = useState<number>(1);
const [currentOrg, setCurrentOrg] = useState<OrgObject>(defaultOrg.currentOrg);
const defaultOrg: OrgContextType = {
currentOrg: {
name: "",
status: "partial",
root_user_email: "",
billing_contact: { email: "", id: -1 },
owner_contact: { email: "", id: -1 },
security_contact: { email: "", id: -1 },
organisation_id: -1,
intake_questionnaire: {
question_one: "",
question_two: "",
question_three: "",
},
},
setCurrentOrg: () => {},
};
const [currentUser, setCurrentUser] = useState<userObject>(
defaultUser.currentUser,
);
const [refreshKey, setRefreshKey] = useState<number>(1);
const [currentOrg, setCurrentOrg] = useState<OrgObject>(
defaultOrg.currentOrg,
);
/*****************************
/*****************************
GETTING CURRENT USER FROM API
*****************************/
// const [current_user, setUser] = useState<User | null>(null);
// const [current_user, setUser] = useState<User | null>(null);
React.useEffect(() => {
(async () => {
if (!auth.user) { return }
try {
const token = auth.user.access_token;
const response = await fetch("/api/v1/user/self/db", {
headers: {
Authorization: `Bearer ${token}`,
},
});
setCurrentUser(await response.json());
} catch (e) {
console.error(e);
}
})();
}, [auth, refreshKey]);
React.useEffect(() => {
(async () => {
if (!auth.user) {
return;
}
try {
const token = auth.user.access_token;
const response = await fetch("/api/v1/user/self/db", {
headers: {
Authorization: `Bearer ${token}`,
},
});
setCurrentUser(await response.json());
} catch (e) {
console.error(e);
}
})();
}, [auth, refreshKey]);
if (!currentUser) {
return (
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Determining your existing user...</div>;
</UserContext.Provider>
);
}
if (!currentUser) {
return(
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Determining your existing user...</div>;
</UserContext.Provider>
);
if (auth.isLoading) {
return (
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Signing you in/out...</div>;
</UserContext.Provider>
);
}
if (!auth.isAuthenticated) {
return (
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Unable to log in</div>;
</UserContext.Provider>
);
}
const onClick: MenuProps["onClick"] = (e) => {
switch (e.key) {
case "profile":
navigate("/profile");
break;
case "orgs":
navigate("/create-org-flow");
break;
case "logout":
auth.removeUser().then(() => navigate("/profile"));
break;
case "bridges":
navigate("/bridges");
break;
default:
console.log("Clicked item:", e.key);
}
};
if (auth.isLoading) {
return(
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Signing you in/out...</div>;
</UserContext.Provider>
);
}
let side_nav_items: MenuItem[] = [];
if (!auth.isAuthenticated) {
return(
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Unable to log in</div>;
</UserContext.Provider>
);
}
const top_nav_items = [
getItem(
currentUser.first_name + " " + currentUser.last_name,
"account",
<UserOutlined />,
[getItem("Account details", "profile"), getItem("Log out", "logout")],
),
];
const onClick: MenuProps['onClick'] = (e) => {
switch (e.key) {
case 'profile':
navigate('/profile');
break;
case 'orgs':
navigate('/create-org-flow');
break;
case 'logout':
auth.removeUser().then(() => navigate('/profile'));
break;
case 'bridges':
navigate('/bridges');
break;
default:
console.log('Clicked item:', e.key);
}
};
let side_nav_items: MenuItem[] = []
const top_nav_items = [
getItem(currentUser.first_name + " " +currentUser.last_name , 'account', <UserOutlined />, [getItem('Account details', 'profile'), getItem('Log out', 'logout')]),
if (currentUser.organisations.length > 0) {
side_nav_items = [
getItem("Onboarding", "orgs", <SettingOutlined />),
getItem("Bridges", "bridges", <DeploymentUnitOutlined />),
];
if (currentUser.organisations.length > 0) {
const currentOrg = currentUser.organisations[0]["name"];
top_nav_items.push(
getItem(currentOrg + " Settings", "orgsettings", <SettingOutlined />),
);
}
side_nav_items = [ getItem('Onboarding', 'orgs', <SettingOutlined />),
getItem('Bridges', 'bridges', <DeploymentUnitOutlined />), ];
const currentOrg = currentUser.organisations[0]['name']
top_nav_items.push(getItem(currentOrg +" Settings", 'orgsettings', <SettingOutlined />));
}
return (
<OrgContext.Provider value={{ currentOrg, setCurrentOrg }}>
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
<Layout>
<Header style={{ display: 'flex', alignItems: 'right' }}>
<div className="demo-logo" >
<h1>SR22</h1>
</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['2']}
items={top_nav_items}
onClick={onClick}
style={{ flex: 1, minWidth: 0, justifyContent:"flex-end" }}
/>
return (
<OrgContext.Provider value={{ currentOrg, setCurrentOrg }}>
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
<Layout>
<Header style={{ display: "flex", alignItems: "right" }}>
<div className="demo-logo">
<h1>SR22</h1>
</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["2"]}
items={top_nav_items}
onClick={onClick}
style={{ flex: 1, minWidth: 0, justifyContent: "flex-end" }}
/>
</Header>
<Layout>
<Sider width={200} style={{ background: colorBgContainer }}>
<Menu
mode="inline"
defaultSelectedKeys={['1']}
defaultOpenKeys={['sub1']}
style={{ height: '100%', borderInlineEnd: 0 }}
items={side_nav_items}
onClick={onClick}
/>
</Sider>
<Layout style={{ padding: '0 24px 24px' }}>
<Breadcrumb
items={[{ title: 'Home' }, { title: location.pathname.substring(1) }]}
style={{ margin: '16px 0' }}
/>
<Content
style={{
padding: 24,
margin: 0,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
<Sider width={200} style={{ background: colorBgContainer }}>
<Menu
mode="inline"
defaultSelectedKeys={["1"]}
defaultOpenKeys={["sub1"]}
style={{ height: "100%", borderInlineEnd: 0 }}
items={side_nav_items}
onClick={onClick}
/>
</Sider>
<Layout style={{ padding: "0 24px 24px" }}>
<Breadcrumb
items={[
{ title: "Home" },
{ title: location.pathname.substring(1) },
]}
style={{ margin: "16px 0" }}
/>
<Content
style={{
padding: 24,
margin: 0,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
<Routes>
<Route path="/" element={<Home />} />
{currentUser && (
<Route path="/profile" element={<Profile />} />
)}
{currentUser && (
<Route
path="/organisations"
element={<Organisations />}
/>
)}
{currentUser && (
<Route path="/create" element={<CreateOrgFlow />} />
)}
<Routes>
<Route path="/" element={<Home/>}/>
{currentUser && <Route path="/profile" element={<Profile/>}/> }
{currentUser && <Route path="/organisations" element={<Organisations/>}/> }
{currentUser && <Route path="/create" element={<CreateOrgFlow/>}/> }
{currentUser && <Route path="/bridges" element={<Bridges/>}/> }
</Routes>
</Content>
</Layout>
{currentUser && (
<Route path="/bridges" element={<Bridges />} />
)}
</Routes>
</Content>
</Layout>
</Layout>
</Layout>
</Layout>
</RefreshContext.Provider>
</UserContext.Provider>
</OrgContext.Provider>
</UserContext.Provider>
</OrgContext.Provider>
);
};
);
}
export default App
export default App;

View file

@ -1,161 +1,203 @@
import { useRef, useState } from 'react';
import { SearchOutlined } from '@ant-design/icons';
import type { InputRef, TableColumnsType, TableColumnType } from 'antd';
import { Button, Input, Space, Table } from 'antd';
import type { FilterDropdownProps } from 'antd/es/table/interface';
import Highlighter from 'react-highlight-words';
import { QRCode } from 'antd';
import { useRef, useState } from "react";
import { SearchOutlined } from "@ant-design/icons";
import type { InputRef, TableColumnsType, TableColumnType } from "antd";
import { Button, Input, Space, Table } from "antd";
import type { FilterDropdownProps } from "antd/es/table/interface";
import Highlighter from "react-highlight-words";
import { QRCode } from "antd";
interface BridgeDataType {
fingerprint: string;
bridgeline: string;
qrCodeText: string;
fingerprint: string;
bridgeline: string;
qrCodeText: string;
}
const Bridges = () => {
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",
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: "",
},
];
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', 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) {
return '["' + bridgeline + '"]';
}
bridgeLines.forEach((line) => {
line.qrCodeText = makeQRCodeText(line.bridgeline);
});
console.log(bridgeLines);
function makeQRCodeText(bridgeline: string) {
return '["' + bridgeline + '"]';
}
const [searchText, setSearchText] = useState("");
const [searchedColumn, setSearchedColumn] = useState("");
const searchInput = useRef<InputRef>(null);
bridgeLines.forEach((line) => {line.qrCodeText = makeQRCodeText(line.bridgeline)});
console.log(bridgeLines);
const handleSearch = (
selectedKeys: string[],
confirm: FilterDropdownProps["confirm"],
dataIndex: DataIndex,
) => {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};
const [searchText, setSearchText] = useState('');
const [searchedColumn, setSearchedColumn] = useState('');
const searchInput = useRef<InputRef>(null);
const handleReset = (clearFilters: () => void) => {
clearFilters();
setSearchText("");
};
const handleSearch = (
selectedKeys: string[],
confirm: FilterDropdownProps['confirm'],
dataIndex: DataIndex,
) => {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};
type DataIndex = keyof BridgeDataType;
const getColumnSearchProps = (
dataIndex: DataIndex,
): TableColumnType<BridgeDataType> => ({
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
close,
}) => (
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
<Input
//ref={searchInput}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={(e) =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
onPressEnter={() =>
handleSearch(selectedKeys as string[], confirm, dataIndex)
}
style={{ marginBottom: 8, display: "block" }}
/>
<Space>
<Button
type="primary"
onClick={() =>
handleSearch(selectedKeys as string[], confirm, dataIndex)
}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button
onClick={() => clearFilters && handleReset(clearFilters)}
size="small"
style={{ width: 90 }}
>
Reset
</Button>
<Button
type="link"
size="small"
onClick={() => {
confirm({ closeDropdown: false });
setSearchText((selectedKeys as string[])[0]);
setSearchedColumn(dataIndex);
}}
>
Filter
</Button>
<Button
type="link"
size="small"
onClick={() => {
close();
}}
>
close
</Button>
</Space>
</div>
),
filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
),
onFilter: (value, record) =>
record[dataIndex]
.toString()
.toLowerCase()
.includes((value as string).toLowerCase()),
filterDropdownProps: {
onOpenChange(open) {
if (open) {
setTimeout(() => searchInput.current?.select(), 100);
}
},
},
render: (text) =>
searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text ? text.toString() : ""}
/>
) : (
text
),
});
const handleReset = (clearFilters: () => void) => {
clearFilters();
setSearchText('');
};
const bridgesColumns: TableColumnsType<BridgeDataType> = [
{
title: "Fingerprint",
dataIndex: "fingerprint",
key: "fingerprint",
width: "30%",
...getColumnSearchProps("fingerprint"),
},
type DataIndex = keyof BridgeDataType;
const getColumnSearchProps = (dataIndex: DataIndex): TableColumnType<BridgeDataType> => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
<Input
//ref={searchInput}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys as string[], confirm, dataIndex)}
style={{ marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys as string[], confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button
onClick={() => clearFilters && handleReset(clearFilters)}
size="small"
style={{ width: 90 }}
>
Reset
</Button>
<Button
type="link"
size="small"
onClick={() => {
confirm({ closeDropdown: false });
setSearchText((selectedKeys as string[])[0]);
setSearchedColumn(dataIndex);
}}
>
Filter
</Button>
<Button
type="link"
size="small"
onClick={() => {
close();
}}
>
close
</Button>
</Space>
</div>
),
filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? '#1677ff' : undefined }} />
),
onFilter: (value, record) =>
record[dataIndex]
.toString()
.toLowerCase()
.includes((value as string).toLowerCase()),
filterDropdownProps: {
onOpenChange(open) {
if (open) {
setTimeout(() => searchInput.current?.select(), 100);
}
},
},
render: (text) =>
searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text ? text.toString() : ''}
/>
) : (
text
),
});
{
title: "Bridgeline",
dataIndex: "bridgeline",
key: "bridgeline",
width: "40%",
...getColumnSearchProps("bridgeline"),
},
const bridgesColumns: TableColumnsType<BridgeDataType> = [
{title: 'Fingerprint',
dataIndex: 'fingerprint',
key: 'fingerprint',
width: '30%',
...getColumnSearchProps('fingerprint')},
{
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(
<>
<Table<BridgeDataType> columns={bridgesColumns} dataSource={bridgeLines} />;
</>);
}
return (
<>
<Table<BridgeDataType>
columns={bridgesColumns}
dataSource={bridgeLines}
/>
;
</>
);
};
export default Bridges;

View file

@ -1,187 +1,254 @@
// src/Posts.jsx
import {useUserContext} from './hooks/UserContext.ts';
import { Button, Form, Input } from 'antd';
import { useUserContext } from "./hooks/UserContext.ts";
import { Button, Form, Input } from "antd";
//import React, {useEffect, useState} from "react";
import {useAuth} from "react-oidc-context";
import {RefreshContext} from "./hooks/RefreshContext.ts";
import {useContext, useState} from "react";
import {useOrgContext} from "./hooks/OrgContext.ts";
import { useAuth } from "react-oidc-context";
import { RefreshContext } from "./hooks/RefreshContext.ts";
import { useContext, useState } from "react";
import { useOrgContext } from "./hooks/OrgContext.ts";
const CreateOrgStep1 = () => {
const {currentUser} = useUserContext();
const [form] = Form.useForm();
const auth = useAuth();
const {refreshKey, setRefreshKey} = useContext(RefreshContext);
const {currentOrg, setCurrentOrg} = useOrgContext();
console.log('context value:', {currentOrg, setCurrentOrg});
const { currentUser } = useUserContext();
const [form] = Form.useForm();
const auth = useAuth();
const { refreshKey, setRefreshKey } = useContext(RefreshContext);
const { currentOrg, setCurrentOrg } = useOrgContext();
console.log("context value:", { currentOrg, setCurrentOrg });
const {TextArea} = Input;
const [currentStep, setCurrentStep] = useState(1);
const { TextArea } = Input;
const [currentStep, setCurrentStep] = useState(1);
async function submitOrgBasicData(values: FormData) {
if (!auth.user) {
return
}
try {
const [name, q1, q2] = Object.values(values);
const data = {
"name": name,
"intake_questionnaire": {
"question_one": q1,
"question_two": q2,
}
}
const token = auth.user.access_token;
const requestOptions = {
method: 'POST',
body: JSON.stringify(data),
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
const response_org = await fetch("/api/v1/org", requestOptions);
const orgID = await response_org.json();
const response_currentOrg = await fetch("/api/v1/org?org_id=" + orgID['id'], {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const currentOrgData = await response_currentOrg.json()
setCurrentOrg(currentOrgData['organisations'][0]);
setCurrentStep(currentStep + 1);
} catch (e) {
console.error(e);
}
async function submitOrgBasicData(values: FormData) {
if (!auth.user) {
return;
}
try {
const [name, q1, q2] = Object.values(values);
const data = {
name: name,
intake_questionnaire: {
question_one: q1,
question_two: q2,
},
};
async function submitOrgBillingData(values: FormData) {
if (!auth.user) {
return
}
try {
const [first_name, last_name, email, phone_number, vat_number, street_address, street_address_line_2, locality] = Object.values(values);
const data = {
"organisation_id": currentOrg.organisation_id,
"contact_type": "billing",
"first_name": first_name,
"last_name": last_name,
"email": email,
"phone_number": phone_number,
"vat_number": vat_number,
"street_address": street_address,
"street_address_line_2": street_address_line_2,
"locality": locality,
}
const token = auth.user.access_token;
const requestOptions = {
method: 'PATCH',
body: JSON.stringify(data),
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
await fetch("/api/v1/org/contact", requestOptions);
setCurrentStep(currentStep + 1);
setRefreshKey(refreshKey +1);
} catch (e) {
console.error(e);
}
const token = auth.user.access_token;
const requestOptions = {
method: "POST",
body: JSON.stringify(data),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
};
const response_org = await fetch("/api/v1/org", requestOptions);
const orgID = await response_org.json();
const response_currentOrg = await fetch(
"/api/v1/org?org_id=" + orgID["id"],
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
},
);
const currentOrgData = await response_currentOrg.json();
setCurrentOrg(currentOrgData["organisations"][0]);
setCurrentStep(currentStep + 1);
} catch (e) {
console.error(e);
}
}
if (!currentUser) {
return <div>Loading...</div>;
async function submitOrgBillingData(values: FormData) {
if (!auth.user) {
return;
}
try {
const [
first_name,
last_name,
email,
phone_number,
vat_number,
street_address,
street_address_line_2,
locality,
] = Object.values(values);
const data = {
organisation_id: currentOrg.organisation_id,
contact_type: "billing",
first_name: first_name,
last_name: last_name,
email: email,
phone_number: phone_number,
vat_number: vat_number,
street_address: street_address,
street_address_line_2: street_address_line_2,
locality: locality,
};
if (currentStep === 1) {
return (
<>
<h2>Step 1 - Basic info</h2>
<Form
layout="vertical"
form={form}
onFinish={submitOrgBasicData}
>
<Form.Item name="Organisation name" label="Organisation name" rules={[{required: true}]}>
<Input/>
</Form.Item>
<h4>Intake Questionnaire</h4>
<Form.Item 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 name="Question 2" 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 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.">
<TextArea showCount maxLength={500}
placeholder="e.g., I worked with SR2 Communications on providing digital security training from 2023-2025."/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</>
);
const token = auth.user.access_token;
const requestOptions = {
method: "PATCH",
body: JSON.stringify(data),
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
};
await fetch("/api/v1/org/contact", requestOptions);
setCurrentStep(currentStep + 1);
setRefreshKey(refreshKey + 1);
} catch (e) {
console.error(e);
}
}
if (currentStep === 2) {
return <>
<h2>Step 2 - Contact and billing details</h2>
if (!currentUser) {
return <div>Loading...</div>;
}
<Form
layout="vertical"
form={form}
onFinish={submitOrgBillingData}
>
<Form.Item name="Billing contact first name" label="Billing contact first name"
rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name="Billing contact last name" label="Billing contact last name"
rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name="Billing contact email" label="Billing contact email address"
rules={[{required: true, type: "email", message: "Not a valid e-mail address."}]}>
<Input/>
</Form.Item>
<Form.Item name="Billing contact phone" label="Billing contact phone number">
<Input/>
</Form.Item>
<Form.Item name="Billing contact VAT number" label="Billing contact VAT number">
<Input/>
</Form.Item>
<Form.Item name="Billing address line 1" label="Billing address line 1" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name="Billing address line 2" label="Billing address line 2">
<Input/>
</Form.Item>
<Form.Item name="locality" label="City" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</>
}
if (currentStep === 1) {
return (
<>
<h2>Step 1 - Basic info</h2>
<Form layout="vertical" form={form} onFinish={submitOrgBasicData}>
<Form.Item
name="Organisation name"
label="Organisation name"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<h4>Intake Questionnaire</h4>
<Form.Item
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
name="Question 2"
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
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."
>
<TextArea
showCount
maxLength={500}
placeholder="e.g., I worked with SR2 Communications on providing digital security training from 2023-2025."
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
</>
);
}
if (currentStep === 3) {
return <> <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>
</>
}
}
if (currentStep === 2) {
return (
<>
<h2>Step 2 - Contact and billing details</h2>
<Form layout="vertical" form={form} onFinish={submitOrgBillingData}>
<Form.Item
name="Billing contact first name"
label="Billing contact first name"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="Billing contact last name"
label="Billing contact last name"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="Billing contact email"
label="Billing contact email address"
rules={[
{
required: true,
type: "email",
message: "Not a valid e-mail address.",
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="Billing contact phone"
label="Billing contact phone number"
>
<Input />
</Form.Item>
<Form.Item
name="Billing contact VAT number"
label="Billing contact VAT number"
>
<Input />
</Form.Item>
<Form.Item
name="Billing address line 1"
label="Billing address line 1"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="Billing address line 2"
label="Billing address line 2"
>
<Input />
</Form.Item>
<Form.Item name="locality" label="City" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
</>
);
}
if (currentStep === 3) {
return (
<>
{" "}
<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;

View file

@ -1,43 +1,60 @@
import {Breadcrumb, Button, theme} from "antd";
import { Breadcrumb, Button, theme } from "antd";
import React from "react";
import {useUserContext} from "./hooks/UserContext.ts";
import {useNavigate} from "react-router";
import { useUserContext } from "./hooks/UserContext.ts";
import { useNavigate } from "react-router";
import Organisations from "./Organisations.tsx";
const Home: React.FC = () => {
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
const navigate = useNavigate();
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
const navigate = useNavigate();
const { currentUser } = useUserContext();
if (currentUser.organisations.length === 0) {
return <>
<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>
<p> If your organanisation is new to SR2 Cloud, start the onboarding process </p>
const { currentUser } = useUserContext();
if (currentUser.organisations.length === 0) {
return (
<>
<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>
<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
return (<>
<Breadcrumb style={{ margin: '16px 0' }} items={[{ title: 'User' }, { title: 'Dashboard' }]} />
<div
//TODO: fix this breadcrumb
return (
<>
<Breadcrumb
style={{ margin: "16px 0" }}
items={[{ title: "User" }, { title: "Dashboard" }]}
/>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
borderRadius: borderRadiusLG,
padding: 24,
minHeight: 360,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
>
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
<div>
<Organisations/>
<Organisations />
</div>
</div>
</>)
}
</div>
</>
);
};
export default Home;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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