feat: prettier format
This commit is contained in:
parent
6eed4dc75d
commit
734dc440c6
15 changed files with 955 additions and 791 deletions
22
README.md
22
README.md
|
|
@ -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...
|
||||
},
|
||||
},
|
||||
])
|
||||
]);
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
])
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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>"
|
||||
}
|
||||
|
|
|
|||
175
src/App.tsx
175
src/App.tsx
|
|
@ -1,31 +1,33 @@
|
|||
|
||||
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';
|
||||
} 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,
|
||||
|
|
@ -41,12 +43,11 @@ function getItem(
|
|||
} as MenuItem;
|
||||
}
|
||||
|
||||
|
||||
const App: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const {
|
||||
token: {colorBgContainer, borderRadiusLG},
|
||||
token: { colorBgContainer, borderRadiusLG },
|
||||
} = theme.useToken();
|
||||
/*******************
|
||||
SIGNING IN WITH OIDC
|
||||
|
|
@ -55,8 +56,11 @@ const App: React.FC = () => {
|
|||
const hasTriedSignin = React.useRef(false);
|
||||
// automatically sign-in
|
||||
React.useEffect(() => {
|
||||
if (!hasAuthParams() &&
|
||||
!auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading &&
|
||||
if (
|
||||
!hasAuthParams() &&
|
||||
!auth.isAuthenticated &&
|
||||
!auth.activeNavigator &&
|
||||
!auth.isLoading &&
|
||||
!hasTriedSignin.current
|
||||
) {
|
||||
auth.signinRedirect();
|
||||
|
|
@ -70,19 +74,19 @@ const App: React.FC = () => {
|
|||
last_name: "",
|
||||
email: "",
|
||||
organisations: [],
|
||||
id: -1
|
||||
id: -1,
|
||||
},
|
||||
setCurrentUser: () => {}
|
||||
}
|
||||
setCurrentUser: () => {},
|
||||
};
|
||||
|
||||
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},
|
||||
billing_contact: { email: "", id: -1 },
|
||||
owner_contact: { email: "", id: -1 },
|
||||
security_contact: { email: "", id: -1 },
|
||||
organisation_id: -1,
|
||||
intake_questionnaire: {
|
||||
question_one: "",
|
||||
|
|
@ -90,13 +94,16 @@ const App: React.FC = () => {
|
|||
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 [currentOrg, setCurrentOrg] = useState<OrgObject>(defaultOrg.currentOrg);
|
||||
|
||||
const [currentOrg, setCurrentOrg] = useState<OrgObject>(
|
||||
defaultOrg.currentOrg,
|
||||
);
|
||||
|
||||
/*****************************
|
||||
GETTING CURRENT USER FROM API
|
||||
|
|
@ -105,7 +112,9 @@ const App: React.FC = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (!auth.user) { return }
|
||||
if (!auth.user) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const token = auth.user.access_token;
|
||||
const response = await fetch("/api/v1/user/self/db", {
|
||||
|
|
@ -120,9 +129,8 @@ const App: React.FC = () => {
|
|||
})();
|
||||
}, [auth, refreshKey]);
|
||||
|
||||
|
||||
if (!currentUser) {
|
||||
return(
|
||||
return (
|
||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||
<div>Determining your existing user...</div>;
|
||||
</UserContext.Provider>
|
||||
|
|
@ -130,7 +138,7 @@ const App: React.FC = () => {
|
|||
}
|
||||
|
||||
if (auth.isLoading) {
|
||||
return(
|
||||
return (
|
||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||
<div>Signing you in/out...</div>;
|
||||
</UserContext.Provider>
|
||||
|
|
@ -138,49 +146,53 @@ const App: React.FC = () => {
|
|||
}
|
||||
|
||||
if (!auth.isAuthenticated) {
|
||||
return(
|
||||
return (
|
||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||
<div>Unable to log in</div>;
|
||||
</UserContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const onClick: MenuProps['onClick'] = (e) => {
|
||||
const onClick: MenuProps["onClick"] = (e) => {
|
||||
switch (e.key) {
|
||||
case 'profile':
|
||||
navigate('/profile');
|
||||
case "profile":
|
||||
navigate("/profile");
|
||||
break;
|
||||
case 'orgs':
|
||||
navigate('/create-org-flow');
|
||||
case "orgs":
|
||||
navigate("/create-org-flow");
|
||||
break;
|
||||
case 'logout':
|
||||
auth.removeUser().then(() => navigate('/profile'));
|
||||
case "logout":
|
||||
auth.removeUser().then(() => navigate("/profile"));
|
||||
break;
|
||||
case 'bridges':
|
||||
navigate('/bridges');
|
||||
case "bridges":
|
||||
navigate("/bridges");
|
||||
break;
|
||||
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 = [
|
||||
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) {
|
||||
side_nav_items = [
|
||||
getItem("Onboarding", "orgs", <SettingOutlined />),
|
||||
getItem("Bridges", "bridges", <DeploymentUnitOutlined />),
|
||||
];
|
||||
|
||||
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 />));
|
||||
|
||||
const currentOrg = currentUser.organisations[0]["name"];
|
||||
top_nav_items.push(
|
||||
getItem(currentOrg + " Settings", "orgsettings", <SettingOutlined />),
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -188,34 +200,37 @@ const App: React.FC = () => {
|
|||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
|
||||
<Layout>
|
||||
<Header style={{ display: 'flex', alignItems: 'right' }}>
|
||||
<div className="demo-logo" >
|
||||
<Header style={{ display: "flex", alignItems: "right" }}>
|
||||
<div className="demo-logo">
|
||||
<h1>SR22</h1>
|
||||
</div>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="horizontal"
|
||||
defaultSelectedKeys={['2']}
|
||||
defaultSelectedKeys={["2"]}
|
||||
items={top_nav_items}
|
||||
onClick={onClick}
|
||||
style={{ flex: 1, minWidth: 0, justifyContent:"flex-end" }}
|
||||
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 }}
|
||||
defaultSelectedKeys={["1"]}
|
||||
defaultOpenKeys={["sub1"]}
|
||||
style={{ height: "100%", borderInlineEnd: 0 }}
|
||||
items={side_nav_items}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout style={{ padding: '0 24px 24px' }}>
|
||||
<Layout style={{ padding: "0 24px 24px" }}>
|
||||
<Breadcrumb
|
||||
items={[{ title: 'Home' }, { title: location.pathname.substring(1) }]}
|
||||
style={{ margin: '16px 0' }}
|
||||
items={[
|
||||
{ title: "Home" },
|
||||
{ title: location.pathname.substring(1) },
|
||||
]}
|
||||
style={{ margin: "16px 0" }}
|
||||
/>
|
||||
<Content
|
||||
style={{
|
||||
|
|
@ -226,15 +241,24 @@ const App: React.FC = () => {
|
|||
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/>}/> }
|
||||
|
||||
{currentUser && <Route path="/bridges" element={<Bridges/>}/> }
|
||||
<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>
|
||||
|
|
@ -243,10 +267,7 @@ const App: React.FC = () => {
|
|||
</RefreshContext.Provider>
|
||||
</UserContext.Provider>
|
||||
</OrgContext.Provider>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
|
|
|||
144
src/Bridges.tsx
144
src/Bridges.tsx
|
|
@ -1,10 +1,10 @@
|
|||
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;
|
||||
|
|
@ -12,29 +12,49 @@ interface BridgeDataType {
|
|||
}
|
||||
|
||||
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: ""},
|
||||
{
|
||||
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)});
|
||||
bridgeLines.forEach((line) => {
|
||||
line.qrCodeText = makeQRCodeText(line.bridgeline);
|
||||
});
|
||||
console.log(bridgeLines);
|
||||
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [searchedColumn, setSearchedColumn] = useState('');
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [searchedColumn, setSearchedColumn] = useState("");
|
||||
const searchInput = useRef<InputRef>(null);
|
||||
|
||||
const handleSearch = (
|
||||
selectedKeys: string[],
|
||||
confirm: FilterDropdownProps['confirm'],
|
||||
confirm: FilterDropdownProps["confirm"],
|
||||
dataIndex: DataIndex,
|
||||
) => {
|
||||
confirm();
|
||||
|
|
@ -44,25 +64,39 @@ const Bridges = () => {
|
|||
|
||||
const handleReset = (clearFilters: () => void) => {
|
||||
clearFilters();
|
||||
setSearchText('');
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
type DataIndex = keyof BridgeDataType;
|
||||
const getColumnSearchProps = (dataIndex: DataIndex): TableColumnType<BridgeDataType> => ({
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
|
||||
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' }}
|
||||
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)}
|
||||
onClick={() =>
|
||||
handleSearch(selectedKeys as string[], confirm, dataIndex)
|
||||
}
|
||||
icon={<SearchOutlined />}
|
||||
size="small"
|
||||
style={{ width: 90 }}
|
||||
|
|
@ -100,7 +134,7 @@ const Bridges = () => {
|
|||
</div>
|
||||
),
|
||||
filterIcon: (filtered: boolean) => (
|
||||
<SearchOutlined style={{ color: filtered ? '#1677ff' : undefined }} />
|
||||
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
|
||||
),
|
||||
onFilter: (value, record) =>
|
||||
record[dataIndex]
|
||||
|
|
@ -117,10 +151,10 @@ const Bridges = () => {
|
|||
render: (text) =>
|
||||
searchedColumn === dataIndex ? (
|
||||
<Highlighter
|
||||
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
|
||||
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
|
||||
searchWords={[searchText]}
|
||||
autoEscape
|
||||
textToHighlight={text ? text.toString() : ''}
|
||||
textToHighlight={text ? text.toString() : ""}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
|
|
@ -128,34 +162,42 @@ const Bridges = () => {
|
|||
});
|
||||
|
||||
const bridgesColumns: TableColumnsType<BridgeDataType> = [
|
||||
{title: 'Fingerprint',
|
||||
dataIndex: 'fingerprint',
|
||||
key: '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: "Fingerprint",
|
||||
dataIndex: "fingerprint",
|
||||
key: "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} />;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return(
|
||||
return (
|
||||
<>
|
||||
|
||||
<Table<BridgeDataType> columns={bridgesColumns} dataSource={bridgeLines} />;
|
||||
</>);
|
||||
|
||||
}
|
||||
<Table<BridgeDataType>
|
||||
columns={bridgesColumns}
|
||||
dataSource={bridgeLines}
|
||||
/>
|
||||
;
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Bridges;
|
||||
|
|
@ -1,59 +1,61 @@
|
|||
// 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 { 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 { refreshKey, setRefreshKey } = useContext(RefreshContext);
|
||||
const { currentOrg, setCurrentOrg } = useOrgContext();
|
||||
console.log("context value:", { currentOrg, setCurrentOrg });
|
||||
|
||||
const {TextArea} = Input;
|
||||
const { TextArea } = Input;
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
|
||||
async function submitOrgBasicData(values: FormData) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const [name, q1, q2] = Object.values(values);
|
||||
const data = {
|
||||
"name": name,
|
||||
"intake_questionnaire": {
|
||||
"question_one": q1,
|
||||
"question_two": q2,
|
||||
}
|
||||
}
|
||||
name: name,
|
||||
intake_questionnaire: {
|
||||
question_one: q1,
|
||||
question_two: q2,
|
||||
},
|
||||
};
|
||||
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
"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'], {
|
||||
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]);
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
const currentOrgData = await response_currentOrg.json();
|
||||
setCurrentOrg(currentOrgData["organisations"][0]);
|
||||
setCurrentStep(currentStep + 1);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
@ -61,36 +63,44 @@ const CreateOrgStep1 = () => {
|
|||
|
||||
async function submitOrgBillingData(values: FormData) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
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 = {
|
||||
"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,
|
||||
}
|
||||
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',
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
await fetch("/api/v1/org/contact", requestOptions);
|
||||
setCurrentStep(currentStep + 1);
|
||||
setRefreshKey(refreshKey +1);
|
||||
|
||||
setRefreshKey(refreshKey + 1);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
@ -104,28 +114,49 @@ const CreateOrgStep1 = () => {
|
|||
return (
|
||||
<>
|
||||
<h2>Step 1 - Basic info</h2>
|
||||
<Form
|
||||
layout="vertical"
|
||||
form={form}
|
||||
onFinish={submitOrgBasicData}
|
||||
<Form layout="vertical" form={form} onFinish={submitOrgBasicData}>
|
||||
<Form.Item
|
||||
name="Organisation name"
|
||||
label="Organisation name"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Form.Item name="Organisation name" label="Organisation name" rules={[{required: true}]}>
|
||||
<Input/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</>
|
||||
|
|
@ -133,55 +164,91 @@ const CreateOrgStep1 = () => {
|
|||
}
|
||||
|
||||
if (currentStep === 2) {
|
||||
return <>
|
||||
return (
|
||||
<>
|
||||
<h2>Step 2 - Contact and billing details</h2>
|
||||
|
||||
<Form
|
||||
layout="vertical"
|
||||
form={form}
|
||||
onFinish={submitOrgBillingData}
|
||||
<Form layout="vertical" form={form} onFinish={submitOrgBillingData}>
|
||||
<Form.Item
|
||||
name="Billing contact first name"
|
||||
label="Billing contact first name"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Form.Item name="Billing contact first name" label="Billing contact first name"
|
||||
rules={[{required: true}]}>
|
||||
<Input/>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="Billing contact last name" label="Billing contact last name"
|
||||
rules={[{required: true}]}>
|
||||
<Input/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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 name="locality" label="City" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">Submit</Button>
|
||||
<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>
|
||||
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;
|
||||
41
src/Home.tsx
41
src/Home.tsx
|
|
@ -1,7 +1,7 @@
|
|||
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 = () => {
|
||||
|
|
@ -12,18 +12,34 @@ const Home: React.FC = () => {
|
|||
|
||||
const { currentUser } = useUserContext();
|
||||
if (currentUser.organisations.length === 0) {
|
||||
return <>
|
||||
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>
|
||||
<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' }]} />
|
||||
return (
|
||||
<>
|
||||
<Breadcrumb
|
||||
style={{ margin: "16px 0" }}
|
||||
items={[{ title: "User" }, { title: "Dashboard" }]}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
|
|
@ -34,10 +50,11 @@ const Home: React.FC = () => {
|
|||
>
|
||||
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
|
||||
<div>
|
||||
<Organisations/>
|
||||
<Organisations />
|
||||
</div>
|
||||
</div>
|
||||
</>)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
|
@ -1,52 +1,55 @@
|
|||
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();
|
||||
const { currentUser } = useUserContext();
|
||||
async function removeUserFromOrg(org_id: number) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
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);
|
||||
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)
|
||||
|
||||
setRefreshKey((prev) => prev + 1);
|
||||
console.log(refreshKey);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function removeOrg(org_id: number) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'DELETE',
|
||||
headers: {Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'}
|
||||
}
|
||||
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)
|
||||
|
||||
setRefreshKey((prev) => prev + 1);
|
||||
console.log(refreshKey);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
@ -54,23 +57,34 @@ const OrgPanel = (props: OrgObject) => {
|
|||
|
||||
return (
|
||||
<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>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>
|
||||
<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>
|
||||
<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)}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => removeUserFromOrg(props.organisation_id)}
|
||||
>
|
||||
Remove yourself from organisation
|
||||
</Button>
|
||||
<Button type="primary" danger onClick={() => removeOrg(props.organisation_id)}>
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={() => removeOrg(props.organisation_id)}
|
||||
>
|
||||
Remove organisation
|
||||
</Button>
|
||||
</Card>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default OrgPanel;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
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();
|
||||
|
|
@ -12,37 +12,39 @@ const Organisations: React.FC = () => {
|
|||
React.useEffect(() => {
|
||||
const fetchOrgData = async () => {
|
||||
console.log("Fetching org data");
|
||||
if (!auth.user) return
|
||||
if (!auth.user) return;
|
||||
try {
|
||||
const requestOptions = {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
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 orgsObject = await response.json();
|
||||
setCurrentOrgData(orgsObject['organisations'])
|
||||
|
||||
setCurrentOrgData(orgsObject["organisations"]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
fetchOrgData()
|
||||
.catch(console.error);
|
||||
};
|
||||
fetchOrgData().catch(console.error);
|
||||
}, [auth.user, refreshKey]);
|
||||
|
||||
console.log(orgData);
|
||||
|
||||
let panels: JSX.Element[] = [];
|
||||
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>
|
||||
{panels}
|
||||
</>);
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Organisations;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// src/Posts.jsx
|
||||
|
||||
import { useUserContext} from './hooks/UserContext.ts';
|
||||
import { useUserContext } from "./hooks/UserContext.ts";
|
||||
|
||||
const Profile = () => {
|
||||
const { currentUser } = useUserContext();
|
||||
|
|
@ -10,14 +10,16 @@ const Profile = () => {
|
|||
}
|
||||
console.log(currentUser);
|
||||
console.log(currentUser.organisations);
|
||||
const userOrgs: { name: string, id: number }[] = currentUser.organisations;
|
||||
const userOrgs: { name: string; id: number }[] = currentUser.organisations;
|
||||
return (
|
||||
<>
|
||||
<p>User: {currentUser.first_name} {currentUser.last_name}</p>
|
||||
<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;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, {createContext, useContext} from 'react';
|
||||
import React, { createContext, useContext } from "react";
|
||||
|
||||
export interface Contact {
|
||||
email: string;
|
||||
|
|
@ -6,18 +6,18 @@ export interface Contact {
|
|||
}
|
||||
|
||||
export interface OrgObject {
|
||||
name: string,
|
||||
status: "partial",
|
||||
root_user_email: string,
|
||||
billing_contact: Contact,
|
||||
owner_contact: Contact,
|
||||
security_contact:Contact,
|
||||
organisation_id: number,
|
||||
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
|
||||
},
|
||||
question_one: string;
|
||||
question_two: string;
|
||||
question_three: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OrgContextType {
|
||||
|
|
@ -30,7 +30,7 @@ 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');
|
||||
throw new Error("Organisation context not found");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, {createContext, useContext} from 'react';
|
||||
import React, { createContext, useContext } from "react";
|
||||
|
||||
export interface RefreshType {
|
||||
refreshKey: number;
|
||||
|
|
@ -13,7 +13,7 @@ export const RefreshContext = createContext<RefreshType>({
|
|||
export const useRefreshContext = () => {
|
||||
const context: RefreshType | undefined = useContext(RefreshContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('Organisation refresh context not found');
|
||||
throw new Error("Organisation refresh context not found");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
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 }[];
|
||||
organisations: { name: string; id: number }[];
|
||||
id: number;
|
||||
}
|
||||
|
||||
|
|
@ -14,14 +13,14 @@ export interface UserContextType {
|
|||
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!');
|
||||
throw new Error("User context not found!");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
28
src/main.tsx
28
src/main.tsx
|
|
@ -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)
|
||||
}
|
||||
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}),
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
};
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<StrictMode>
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<AuthProvider {...oidcConfig}>
|
||||
<BrowserRouter basename="/ui">
|
||||
<App/>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
</StrictMode>,)
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue