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
|
```js
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(["dist"]),
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
extends: [
|
extends: [
|
||||||
// Other configs...
|
// Other configs...
|
||||||
|
|
||||||
|
|
@ -34,40 +34,40 @@ export default defineConfig([
|
||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
// other options...
|
// other options...
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// eslint.config.js
|
// eslint.config.js
|
||||||
import reactX from 'eslint-plugin-react-x'
|
import reactX from "eslint-plugin-react-x";
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
import reactDom from "eslint-plugin-react-dom";
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(["dist"]),
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
extends: [
|
extends: [
|
||||||
// Other configs...
|
// Other configs...
|
||||||
// Enable lint rules for React
|
// Enable lint rules for React
|
||||||
reactX.configs['recommended-typescript'],
|
reactX.configs["recommended-typescript"],
|
||||||
// Enable lint rules for React DOM
|
// Enable lint rules for React DOM
|
||||||
reactDom.configs.recommended,
|
reactDom.configs.recommended,
|
||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
// other options...
|
// other options...
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import js from '@eslint/js'
|
import js from "@eslint/js";
|
||||||
import globals from 'globals'
|
import globals from "globals";
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from "typescript-eslint";
|
||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
import eslintConfigPrettier from "eslint-config-prettier";
|
import eslintConfigPrettier from "eslint-config-prettier";
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(["dist"]),
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
extends: [
|
extends: [
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended,
|
||||||
|
|
@ -21,4 +21,4 @@ export default defineConfig([
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": ["config:recommended"],
|
||||||
"config:recommended"
|
|
||||||
],
|
|
||||||
"minimumReleaseAge": "14 days",
|
"minimumReleaseAge": "14 days",
|
||||||
"gitAuthor": "Renovate<noreply@sr2.uk>"
|
"gitAuthor": "Renovate<noreply@sr2.uk>"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
441
src/App.tsx
441
src/App.tsx
|
|
@ -1,252 +1,273 @@
|
||||||
|
import "./App.css";
|
||||||
import './App.css'
|
|
||||||
import { useAuth, hasAuthParams } from "react-oidc-context";
|
import { useAuth, hasAuthParams } from "react-oidc-context";
|
||||||
import React, {useState} from 'react';
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
DeploymentUnitOutlined,
|
DeploymentUnitOutlined,
|
||||||
} from '@ant-design/icons';
|
} from "@ant-design/icons";
|
||||||
import {Breadcrumb, type MenuProps} from 'antd';
|
import { Breadcrumb, type MenuProps } from "antd";
|
||||||
import { Layout, Menu, theme } from 'antd';
|
import { Layout, Menu, theme } from "antd";
|
||||||
import Home from "./Home.tsx";
|
import Home from "./Home.tsx";
|
||||||
import {Route, Routes, useLocation, useNavigate} from "react-router";
|
import { Route, Routes, useLocation, useNavigate } from "react-router";
|
||||||
import Profile from "./Profile.tsx";
|
import Profile from "./Profile.tsx";
|
||||||
import {type UserContextType} from './hooks/UserContext.ts';
|
import { type UserContextType } from "./hooks/UserContext.ts";
|
||||||
import {UserContext} from './hooks/UserContext.ts';
|
import { UserContext } from "./hooks/UserContext.ts";
|
||||||
import type {userObject} from "./hooks/UserContext.ts";
|
import type { userObject } from "./hooks/UserContext.ts";
|
||||||
|
|
||||||
import Organisations from "./Organisations.tsx";
|
import Organisations from "./Organisations.tsx";
|
||||||
import {RefreshContext} from "./hooks/RefreshContext.ts";
|
import { RefreshContext } from "./hooks/RefreshContext.ts";
|
||||||
import Bridges from "./Bridges.tsx";
|
import Bridges from "./Bridges.tsx";
|
||||||
import {OrgContext, type OrgContextType, type OrgObject} from "./hooks/OrgContext.ts";
|
import {
|
||||||
|
OrgContext,
|
||||||
|
type OrgContextType,
|
||||||
|
type OrgObject,
|
||||||
|
} from "./hooks/OrgContext.ts";
|
||||||
import CreateOrgFlow from "./CreateOrgFlow.tsx";
|
import CreateOrgFlow from "./CreateOrgFlow.tsx";
|
||||||
|
|
||||||
const { Header, Content, Sider } = Layout;
|
const { Header, Content, Sider } = Layout;
|
||||||
|
|
||||||
type MenuItem = Required<MenuProps>['items'][number];
|
type MenuItem = Required<MenuProps>["items"][number];
|
||||||
|
|
||||||
|
|
||||||
function getItem(
|
function getItem(
|
||||||
label: React.ReactNode,
|
label: React.ReactNode,
|
||||||
key: React.Key,
|
key: React.Key,
|
||||||
icon?: React.ReactNode,
|
icon?: React.ReactNode,
|
||||||
children?: MenuItem[],
|
children?: MenuItem[],
|
||||||
): MenuItem {
|
): MenuItem {
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
icon,
|
icon,
|
||||||
children,
|
children,
|
||||||
label,
|
label,
|
||||||
} as MenuItem;
|
} as MenuItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const {
|
const {
|
||||||
token: {colorBgContainer, borderRadiusLG},
|
token: { colorBgContainer, borderRadiusLG },
|
||||||
} = theme.useToken();
|
} = theme.useToken();
|
||||||
/*******************
|
/*******************
|
||||||
SIGNING IN WITH OIDC
|
SIGNING IN WITH OIDC
|
||||||
*******************/
|
*******************/
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const hasTriedSignin = React.useRef(false);
|
const hasTriedSignin = React.useRef(false);
|
||||||
// automatically sign-in
|
// automatically sign-in
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!hasAuthParams() &&
|
if (
|
||||||
!auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading &&
|
!hasAuthParams() &&
|
||||||
!hasTriedSignin.current
|
!auth.isAuthenticated &&
|
||||||
) {
|
!auth.activeNavigator &&
|
||||||
auth.signinRedirect();
|
!auth.isLoading &&
|
||||||
hasTriedSignin.current = true;
|
!hasTriedSignin.current
|
||||||
}
|
) {
|
||||||
}, [auth]);
|
auth.signinRedirect();
|
||||||
|
hasTriedSignin.current = true;
|
||||||
const defaultUser: UserContextType = {
|
|
||||||
currentUser: {
|
|
||||||
first_name: "",
|
|
||||||
last_name: "",
|
|
||||||
email: "",
|
|
||||||
organisations: [],
|
|
||||||
id: -1
|
|
||||||
},
|
|
||||||
setCurrentUser: () => {}
|
|
||||||
}
|
}
|
||||||
|
}, [auth]);
|
||||||
|
|
||||||
const defaultOrg: OrgContextType = {
|
const defaultUser: UserContextType = {
|
||||||
currentOrg: {
|
currentUser: {
|
||||||
name: "",
|
first_name: "",
|
||||||
status: "partial",
|
last_name: "",
|
||||||
root_user_email: "",
|
email: "",
|
||||||
billing_contact: {email: "", id: -1},
|
organisations: [],
|
||||||
owner_contact: {email: "", id: -1},
|
id: -1,
|
||||||
security_contact: {email: "", id: -1},
|
},
|
||||||
organisation_id: -1,
|
setCurrentUser: () => {},
|
||||||
intake_questionnaire: {
|
};
|
||||||
question_one: "",
|
|
||||||
question_two: "",
|
|
||||||
question_three: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setCurrentOrg: () => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [currentUser, setCurrentUser] = useState<userObject>(defaultUser.currentUser);
|
const defaultOrg: OrgContextType = {
|
||||||
const [refreshKey, setRefreshKey] = useState<number>(1);
|
currentOrg: {
|
||||||
const [currentOrg, setCurrentOrg] = useState<OrgObject>(defaultOrg.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
|
GETTING CURRENT USER FROM API
|
||||||
*****************************/
|
*****************************/
|
||||||
// const [current_user, setUser] = useState<User | null>(null);
|
// const [current_user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (!auth.user) { return }
|
if (!auth.user) {
|
||||||
try {
|
return;
|
||||||
const token = auth.user.access_token;
|
}
|
||||||
const response = await fetch("/api/v1/user/self/db", {
|
try {
|
||||||
headers: {
|
const token = auth.user.access_token;
|
||||||
Authorization: `Bearer ${token}`,
|
const response = await fetch("/api/v1/user/self/db", {
|
||||||
},
|
headers: {
|
||||||
});
|
Authorization: `Bearer ${token}`,
|
||||||
setCurrentUser(await response.json());
|
},
|
||||||
} catch (e) {
|
});
|
||||||
console.error(e);
|
setCurrentUser(await response.json());
|
||||||
}
|
} catch (e) {
|
||||||
})();
|
console.error(e);
|
||||||
}, [auth, refreshKey]);
|
}
|
||||||
|
})();
|
||||||
|
}, [auth, refreshKey]);
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||||
|
<div>Determining your existing user...</div>;
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!currentUser) {
|
if (auth.isLoading) {
|
||||||
return(
|
return (
|
||||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||||
<div>Determining your existing user...</div>;
|
<div>Signing you in/out...</div>;
|
||||||
</UserContext.Provider>
|
</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) {
|
let side_nav_items: MenuItem[] = [];
|
||||||
return(
|
|
||||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
|
||||||
<div>Signing you in/out...</div>;
|
|
||||||
</UserContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!auth.isAuthenticated) {
|
const top_nav_items = [
|
||||||
return(
|
getItem(
|
||||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
currentUser.first_name + " " + currentUser.last_name,
|
||||||
<div>Unable to log in</div>;
|
"account",
|
||||||
</UserContext.Provider>
|
<UserOutlined />,
|
||||||
);
|
[getItem("Account details", "profile"), getItem("Log out", "logout")],
|
||||||
}
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (currentUser.organisations.length > 0) {
|
||||||
|
side_nav_items = [
|
||||||
const onClick: MenuProps['onClick'] = (e) => {
|
getItem("Onboarding", "orgs", <SettingOutlined />),
|
||||||
switch (e.key) {
|
getItem("Bridges", "bridges", <DeploymentUnitOutlined />),
|
||||||
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) {
|
const currentOrg = currentUser.organisations[0]["name"];
|
||||||
|
top_nav_items.push(
|
||||||
|
getItem(currentOrg + " Settings", "orgsettings", <SettingOutlined />),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
side_nav_items = [ getItem('Onboarding', 'orgs', <SettingOutlined />),
|
return (
|
||||||
getItem('Bridges', 'bridges', <DeploymentUnitOutlined />), ];
|
<OrgContext.Provider value={{ currentOrg, setCurrentOrg }}>
|
||||||
|
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||||
const currentOrg = currentUser.organisations[0]['name']
|
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
|
||||||
top_nav_items.push(getItem(currentOrg +" Settings", 'orgsettings', <SettingOutlined />));
|
<Layout>
|
||||||
|
<Header style={{ display: "flex", alignItems: "right" }}>
|
||||||
}
|
<div className="demo-logo">
|
||||||
|
<h1>SR22</h1>
|
||||||
return (
|
</div>
|
||||||
<OrgContext.Provider value={{ currentOrg, setCurrentOrg }}>
|
<Menu
|
||||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
theme="dark"
|
||||||
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
|
mode="horizontal"
|
||||||
<Layout>
|
defaultSelectedKeys={["2"]}
|
||||||
<Header style={{ display: 'flex', alignItems: 'right' }}>
|
items={top_nav_items}
|
||||||
<div className="demo-logo" >
|
onClick={onClick}
|
||||||
<h1>SR22</h1>
|
style={{ flex: 1, minWidth: 0, justifyContent: "flex-end" }}
|
||||||
</div>
|
/>
|
||||||
<Menu
|
|
||||||
theme="dark"
|
|
||||||
mode="horizontal"
|
|
||||||
defaultSelectedKeys={['2']}
|
|
||||||
items={top_nav_items}
|
|
||||||
onClick={onClick}
|
|
||||||
style={{ flex: 1, minWidth: 0, justifyContent:"flex-end" }}
|
|
||||||
/>
|
|
||||||
</Header>
|
</Header>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Sider width={200} style={{ background: colorBgContainer }}>
|
<Sider width={200} style={{ background: colorBgContainer }}>
|
||||||
<Menu
|
<Menu
|
||||||
mode="inline"
|
mode="inline"
|
||||||
defaultSelectedKeys={['1']}
|
defaultSelectedKeys={["1"]}
|
||||||
defaultOpenKeys={['sub1']}
|
defaultOpenKeys={["sub1"]}
|
||||||
style={{ height: '100%', borderInlineEnd: 0 }}
|
style={{ height: "100%", borderInlineEnd: 0 }}
|
||||||
items={side_nav_items}
|
items={side_nav_items}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
</Sider>
|
</Sider>
|
||||||
<Layout style={{ padding: '0 24px 24px' }}>
|
<Layout style={{ padding: "0 24px 24px" }}>
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
items={[{ title: 'Home' }, { title: location.pathname.substring(1) }]}
|
items={[
|
||||||
style={{ margin: '16px 0' }}
|
{ title: "Home" },
|
||||||
/>
|
{ title: location.pathname.substring(1) },
|
||||||
<Content
|
]}
|
||||||
style={{
|
style={{ margin: "16px 0" }}
|
||||||
padding: 24,
|
/>
|
||||||
margin: 0,
|
<Content
|
||||||
minHeight: 280,
|
style={{
|
||||||
background: colorBgContainer,
|
padding: 24,
|
||||||
borderRadius: borderRadiusLG,
|
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>
|
{currentUser && (
|
||||||
<Route path="/" element={<Home/>}/>
|
<Route path="/bridges" element={<Bridges />} />
|
||||||
{currentUser && <Route path="/profile" element={<Profile/>}/> }
|
)}
|
||||||
{currentUser && <Route path="/organisations" element={<Organisations/>}/> }
|
</Routes>
|
||||||
{currentUser && <Route path="/create" element={<CreateOrgFlow/>}/> }
|
</Content>
|
||||||
|
</Layout>
|
||||||
{currentUser && <Route path="/bridges" element={<Bridges/>}/> }
|
|
||||||
|
|
||||||
</Routes>
|
|
||||||
</Content>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</RefreshContext.Provider>
|
</RefreshContext.Provider>
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
</OrgContext.Provider>
|
</OrgContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
);
|
export default App;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default App
|
|
||||||
|
|
|
||||||
332
src/Bridges.tsx
332
src/Bridges.tsx
|
|
@ -1,161 +1,203 @@
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from "react";
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from "@ant-design/icons";
|
||||||
import type { InputRef, TableColumnsType, TableColumnType } from 'antd';
|
import type { InputRef, TableColumnsType, TableColumnType } from "antd";
|
||||||
import { Button, Input, Space, Table } from 'antd';
|
import { Button, Input, Space, Table } from "antd";
|
||||||
import type { FilterDropdownProps } from 'antd/es/table/interface';
|
import type { FilterDropdownProps } from "antd/es/table/interface";
|
||||||
import Highlighter from 'react-highlight-words';
|
import Highlighter from "react-highlight-words";
|
||||||
import { QRCode } from 'antd';
|
import { QRCode } from "antd";
|
||||||
interface BridgeDataType {
|
interface BridgeDataType {
|
||||||
fingerprint: string;
|
fingerprint: string;
|
||||||
bridgeline: string;
|
bridgeline: string;
|
||||||
qrCodeText: string;
|
qrCodeText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bridges = () => {
|
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[] = [
|
function makeQRCodeText(bridgeline: string) {
|
||||||
{fingerprint: '0C6DDE3F9FC377A5E69DC044E81C857277148D71', bridgeline: "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0", qrCodeText: "" },
|
return '["' + bridgeline + '"]';
|
||||||
{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: ""},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
bridgeLines.forEach((line) => {
|
||||||
|
line.qrCodeText = makeQRCodeText(line.bridgeline);
|
||||||
|
});
|
||||||
|
console.log(bridgeLines);
|
||||||
|
|
||||||
function makeQRCodeText(bridgeline: string) {
|
const [searchText, setSearchText] = useState("");
|
||||||
return '["' + bridgeline + '"]';
|
const [searchedColumn, setSearchedColumn] = useState("");
|
||||||
}
|
const searchInput = useRef<InputRef>(null);
|
||||||
|
|
||||||
bridgeLines.forEach((line) => {line.qrCodeText = makeQRCodeText(line.bridgeline)});
|
const handleSearch = (
|
||||||
console.log(bridgeLines);
|
selectedKeys: string[],
|
||||||
|
confirm: FilterDropdownProps["confirm"],
|
||||||
|
dataIndex: DataIndex,
|
||||||
|
) => {
|
||||||
|
confirm();
|
||||||
|
setSearchText(selectedKeys[0]);
|
||||||
|
setSearchedColumn(dataIndex);
|
||||||
|
};
|
||||||
|
|
||||||
const [searchText, setSearchText] = useState('');
|
const handleReset = (clearFilters: () => void) => {
|
||||||
const [searchedColumn, setSearchedColumn] = useState('');
|
clearFilters();
|
||||||
const searchInput = useRef<InputRef>(null);
|
setSearchText("");
|
||||||
|
};
|
||||||
|
|
||||||
const handleSearch = (
|
type DataIndex = keyof BridgeDataType;
|
||||||
selectedKeys: string[],
|
const getColumnSearchProps = (
|
||||||
confirm: FilterDropdownProps['confirm'],
|
dataIndex: DataIndex,
|
||||||
dataIndex: DataIndex,
|
): TableColumnType<BridgeDataType> => ({
|
||||||
) => {
|
filterDropdown: ({
|
||||||
confirm();
|
setSelectedKeys,
|
||||||
setSearchText(selectedKeys[0]);
|
selectedKeys,
|
||||||
setSearchedColumn(dataIndex);
|
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) => {
|
const bridgesColumns: TableColumnsType<BridgeDataType> = [
|
||||||
clearFilters();
|
{
|
||||||
setSearchText('');
|
title: "Fingerprint",
|
||||||
};
|
dataIndex: "fingerprint",
|
||||||
|
key: "fingerprint",
|
||||||
|
width: "30%",
|
||||||
|
...getColumnSearchProps("fingerprint"),
|
||||||
|
},
|
||||||
|
|
||||||
type DataIndex = keyof BridgeDataType;
|
{
|
||||||
const getColumnSearchProps = (dataIndex: DataIndex): TableColumnType<BridgeDataType> => ({
|
title: "Bridgeline",
|
||||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
|
dataIndex: "bridgeline",
|
||||||
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
|
key: "bridgeline",
|
||||||
<Input
|
width: "40%",
|
||||||
//ref={searchInput}
|
...getColumnSearchProps("bridgeline"),
|
||||||
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 bridgesColumns: TableColumnsType<BridgeDataType> = [
|
{
|
||||||
{title: 'Fingerprint',
|
title: "QR Code",
|
||||||
dataIndex: 'fingerprint',
|
width: "40%",
|
||||||
key: 'fingerprint',
|
key: "qrCodeText",
|
||||||
width: '30%',
|
dataIndex: "qrCodeText",
|
||||||
...getColumnSearchProps('fingerprint')},
|
render: (_text, record) => {
|
||||||
|
return <QRCode value={record.qrCodeText} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
{title: 'Bridgeline',
|
return (
|
||||||
dataIndex: 'bridgeline',
|
<>
|
||||||
key: 'bridgeline',
|
<Table<BridgeDataType>
|
||||||
width: '40%',
|
columns={bridgesColumns}
|
||||||
...getColumnSearchProps('bridgeline')},
|
dataSource={bridgeLines}
|
||||||
|
/>
|
||||||
{title: 'QR Code',
|
;
|
||||||
width: '40%',
|
</>
|
||||||
key: 'qrCodeText',
|
);
|
||||||
dataIndex: 'qrCodeText',
|
};
|
||||||
render: (_text, record) => {return (<QRCode value={record.qrCodeText} />)}
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
|
|
||||||
<Table<BridgeDataType> columns={bridgesColumns} dataSource={bridgeLines} />;
|
|
||||||
</>);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Bridges;
|
export default Bridges;
|
||||||
|
|
@ -1,187 +1,254 @@
|
||||||
// src/Posts.jsx
|
// src/Posts.jsx
|
||||||
|
|
||||||
import {useUserContext} from './hooks/UserContext.ts';
|
import { useUserContext } from "./hooks/UserContext.ts";
|
||||||
import { Button, Form, Input } from 'antd';
|
import { Button, Form, Input } from "antd";
|
||||||
//import React, {useEffect, useState} from "react";
|
//import React, {useEffect, useState} from "react";
|
||||||
import {useAuth} from "react-oidc-context";
|
import { useAuth } from "react-oidc-context";
|
||||||
import {RefreshContext} from "./hooks/RefreshContext.ts";
|
import { RefreshContext } from "./hooks/RefreshContext.ts";
|
||||||
import {useContext, useState} from "react";
|
import { useContext, useState } from "react";
|
||||||
import {useOrgContext} from "./hooks/OrgContext.ts";
|
import { useOrgContext } from "./hooks/OrgContext.ts";
|
||||||
|
|
||||||
const CreateOrgStep1 = () => {
|
const CreateOrgStep1 = () => {
|
||||||
const {currentUser} = useUserContext();
|
const { currentUser } = useUserContext();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const {refreshKey, setRefreshKey} = useContext(RefreshContext);
|
const { refreshKey, setRefreshKey } = useContext(RefreshContext);
|
||||||
const {currentOrg, setCurrentOrg} = useOrgContext();
|
const { currentOrg, setCurrentOrg } = useOrgContext();
|
||||||
console.log('context value:', {currentOrg, setCurrentOrg});
|
console.log("context value:", { currentOrg, setCurrentOrg });
|
||||||
|
|
||||||
const {TextArea} = Input;
|
const { TextArea } = Input;
|
||||||
const [currentStep, setCurrentStep] = useState(1);
|
const [currentStep, setCurrentStep] = useState(1);
|
||||||
|
|
||||||
async function submitOrgBasicData(values: FormData) {
|
async function submitOrgBasicData(values: FormData) {
|
||||||
if (!auth.user) {
|
if (!auth.user) {
|
||||||
return
|
return;
|
||||||
}
|
|
||||||
try {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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) {
|
const token = auth.user.access_token;
|
||||||
if (!auth.user) {
|
const requestOptions = {
|
||||||
return
|
method: "POST",
|
||||||
}
|
body: JSON.stringify(data),
|
||||||
try {
|
headers: {
|
||||||
const [first_name, last_name, email, phone_number, vat_number, street_address, street_address_line_2, locality] = Object.values(values);
|
Authorization: `Bearer ${token}`,
|
||||||
const data = {
|
"Content-Type": "application/json",
|
||||||
"organisation_id": currentOrg.organisation_id,
|
},
|
||||||
"contact_type": "billing",
|
};
|
||||||
"first_name": first_name,
|
const response_org = await fetch("/api/v1/org", requestOptions);
|
||||||
"last_name": last_name,
|
const orgID = await response_org.json();
|
||||||
"email": email,
|
const response_currentOrg = await fetch(
|
||||||
"phone_number": phone_number,
|
"/api/v1/org?org_id=" + orgID["id"],
|
||||||
"vat_number": vat_number,
|
{
|
||||||
"street_address": street_address,
|
headers: {
|
||||||
"street_address_line_2": street_address_line_2,
|
Authorization: `Bearer ${token}`,
|
||||||
"locality": locality,
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
|
},
|
||||||
const token = auth.user.access_token;
|
);
|
||||||
const requestOptions = {
|
const currentOrgData = await response_currentOrg.json();
|
||||||
method: 'PATCH',
|
setCurrentOrg(currentOrgData["organisations"][0]);
|
||||||
body: JSON.stringify(data),
|
setCurrentStep(currentStep + 1);
|
||||||
headers: {
|
} catch (e) {
|
||||||
Authorization: `Bearer ${token}`,
|
console.error(e);
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await fetch("/api/v1/org/contact", requestOptions);
|
|
||||||
setCurrentStep(currentStep + 1);
|
|
||||||
setRefreshKey(refreshKey +1);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!currentUser) {
|
async function submitOrgBillingData(values: FormData) {
|
||||||
return <div>Loading...</div>;
|
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) {
|
const token = auth.user.access_token;
|
||||||
return (
|
const requestOptions = {
|
||||||
<>
|
method: "PATCH",
|
||||||
<h2>Step 1 - Basic info</h2>
|
body: JSON.stringify(data),
|
||||||
<Form
|
headers: {
|
||||||
layout="vertical"
|
Authorization: `Bearer ${token}`,
|
||||||
form={form}
|
"Content-Type": "application/json",
|
||||||
onFinish={submitOrgBasicData}
|
},
|
||||||
>
|
};
|
||||||
<Form.Item name="Organisation name" label="Organisation name" rules={[{required: true}]}>
|
await fetch("/api/v1/org/contact", requestOptions);
|
||||||
<Input/>
|
setCurrentStep(currentStep + 1);
|
||||||
</Form.Item>
|
setRefreshKey(refreshKey + 1);
|
||||||
<h4>Intake Questionnaire</h4>
|
} catch (e) {
|
||||||
<Form.Item name="Question 1" label="What region(s) does your organisation operate in?">
|
console.error(e);
|
||||||
<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 === 2) {
|
if (!currentUser) {
|
||||||
return <>
|
return <div>Loading...</div>;
|
||||||
<h2>Step 2 - Contact and billing details</h2>
|
}
|
||||||
|
|
||||||
<Form
|
if (currentStep === 1) {
|
||||||
layout="vertical"
|
return (
|
||||||
form={form}
|
<>
|
||||||
onFinish={submitOrgBillingData}
|
<h2>Step 1 - Basic info</h2>
|
||||||
>
|
<Form layout="vertical" form={form} onFinish={submitOrgBasicData}>
|
||||||
<Form.Item name="Billing contact first name" label="Billing contact first name"
|
<Form.Item
|
||||||
rules={[{required: true}]}>
|
name="Organisation name"
|
||||||
<Input/>
|
label="Organisation name"
|
||||||
</Form.Item>
|
rules={[{ required: true }]}
|
||||||
<Form.Item name="Billing contact last name" label="Billing contact last name"
|
>
|
||||||
rules={[{required: true}]}>
|
<Input />
|
||||||
<Input/>
|
</Form.Item>
|
||||||
</Form.Item>
|
<h4>Intake Questionnaire</h4>
|
||||||
<Form.Item name="Billing contact email" label="Billing contact email address"
|
<Form.Item
|
||||||
rules={[{required: true, type: "email", message: "Not a valid e-mail address."}]}>
|
name="Question 1"
|
||||||
<Input/>
|
label="What region(s) does your organisation operate in?"
|
||||||
</Form.Item>
|
>
|
||||||
<Form.Item name="Billing contact phone" label="Billing contact phone number">
|
<TextArea
|
||||||
<Input/>
|
showCount
|
||||||
</Form.Item>
|
maxLength={100}
|
||||||
<Form.Item name="Billing contact VAT number" label="Billing contact VAT number">
|
placeholder="e.g., Southern Africa"
|
||||||
<Input/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="Billing address line 1" label="Billing address line 1" rules={[{required: true}]}>
|
<Form.Item
|
||||||
<Input/>
|
name="Question 2"
|
||||||
</Form.Item>
|
label="Briefly describe how your organisation supports civil society."
|
||||||
<Form.Item name="Billing address line 2" label="Billing address line 2">
|
>
|
||||||
<Input/>
|
<TextArea
|
||||||
</Form.Item>
|
showCount
|
||||||
<Form.Item name="locality" label="City" rules={[{required: true}]}>
|
maxLength={300}
|
||||||
<Input/>
|
placeholder="e.g., Our organisation supports various groups such as ..."
|
||||||
</Form.Item>
|
/>
|
||||||
<Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" htmlType="submit">Submit</Button>
|
<Form.Item
|
||||||
</Form.Item>
|
name="Question 3"
|
||||||
</Form>
|
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) {
|
if (currentStep === 2) {
|
||||||
return <> <h2>Thank you for submitting your organisation info. </h2>
|
return (
|
||||||
<p> This will be manually reviewed by a member of SR2 Cloud and you will be contacted shortly.
|
<>
|
||||||
We aim to complete reviews within 24 hours. You can change your answers at any point from the
|
<h2>Step 2 - Contact and billing details</h2>
|
||||||
'My organisations' section.</p>
|
|
||||||
</>
|
<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;
|
export default CreateOrgStep1;
|
||||||
77
src/Home.tsx
77
src/Home.tsx
|
|
@ -1,43 +1,60 @@
|
||||||
import {Breadcrumb, Button, theme} from "antd";
|
import { Breadcrumb, Button, theme } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {useUserContext} from "./hooks/UserContext.ts";
|
import { useUserContext } from "./hooks/UserContext.ts";
|
||||||
import {useNavigate} from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import Organisations from "./Organisations.tsx";
|
import Organisations from "./Organisations.tsx";
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
token: { colorBgContainer, borderRadiusLG },
|
token: { colorBgContainer, borderRadiusLG },
|
||||||
} = theme.useToken();
|
} = theme.useToken();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { currentUser } = useUserContext();
|
const { currentUser } = useUserContext();
|
||||||
if (currentUser.organisations.length === 0) {
|
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>
|
<h2>Welcome to SR2 Cloud! </h2>
|
||||||
<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
|
//TODO: fix this breadcrumb
|
||||||
return (<>
|
return (
|
||||||
<Breadcrumb style={{ margin: '16px 0' }} items={[{ title: 'User' }, { title: 'Dashboard' }]} />
|
<>
|
||||||
<div
|
<Breadcrumb
|
||||||
|
style={{ margin: "16px 0" }}
|
||||||
|
items={[{ title: "User" }, { title: "Dashboard" }]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: 24,
|
padding: 24,
|
||||||
minHeight: 360,
|
minHeight: 360,
|
||||||
background: colorBgContainer,
|
background: colorBgContainer,
|
||||||
borderRadius: borderRadiusLG,
|
borderRadius: borderRadiusLG,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
|
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
|
||||||
<div>
|
<div>
|
||||||
<Organisations/>
|
<Organisations />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>)
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
152
src/OrgPanel.tsx
152
src/OrgPanel.tsx
|
|
@ -1,76 +1,90 @@
|
||||||
import {Button, Card, Space} from 'antd';
|
import { Button, Card, Space } from "antd";
|
||||||
import type {OrgObject} from "./hooks/OrgContext.ts";
|
import type { OrgObject } from "./hooks/OrgContext.ts";
|
||||||
import {useUserContext} from "./hooks/UserContext.ts";
|
import { useUserContext } from "./hooks/UserContext.ts";
|
||||||
import {useAuth} from "react-oidc-context";
|
import { useAuth } from "react-oidc-context";
|
||||||
import {useContext} from "react";
|
import { useContext } from "react";
|
||||||
import {RefreshContext} from "./hooks/RefreshContext.ts";
|
import { RefreshContext } from "./hooks/RefreshContext.ts";
|
||||||
|
|
||||||
const OrgPanel = (props: OrgObject) => {
|
const OrgPanel = (props: OrgObject) => {
|
||||||
|
const auth = useAuth();
|
||||||
const auth = useAuth();
|
const { refreshKey, setRefreshKey } = useContext(RefreshContext);
|
||||||
const { refreshKey, setRefreshKey } = useContext(RefreshContext);
|
const { currentUser } = useUserContext();
|
||||||
const {currentUser} = useUserContext();
|
async function removeUserFromOrg(org_id: number) {
|
||||||
async function removeUserFromOrg(org_id: number) {
|
if (!auth.user) {
|
||||||
if (!auth.user) {
|
return;
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const token = auth.user.access_token;
|
||||||
async function removeOrg(org_id: number) {
|
const requestOptions = {
|
||||||
if (!auth.user) {
|
method: "DELETE",
|
||||||
return
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${token}`,
|
||||||
try {
|
"Content-Type": "application/json",
|
||||||
const token = auth.user.access_token;
|
},
|
||||||
const requestOptions = {
|
};
|
||||||
method: 'DELETE',
|
const response = await fetch(
|
||||||
headers: {Authorization: `Bearer ${token}`,
|
"/api/v1/org/user?org_id=" + org_id + "&user_id=" + currentUser.id,
|
||||||
'Content-Type': 'application/json'}
|
requestOptions,
|
||||||
}
|
);
|
||||||
await fetch("/api/v1/org/self?org_id=" + org_id, requestOptions);
|
await response.json();
|
||||||
setRefreshKey(prev => prev + 1);
|
setRefreshKey((prev) => prev + 1);
|
||||||
console.log(refreshKey)
|
console.log(refreshKey);
|
||||||
|
} catch (e) {
|
||||||
} catch (e) {
|
console.error(e);
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
async function removeOrg(org_id: number) {
|
||||||
<Space vertical size={16}>
|
if (!auth.user) {
|
||||||
<Card title={props.name} extra={<a href="#">More</a>} style={{width: 300}}>
|
return;
|
||||||
<p>Validated? {props.status}</p>
|
}
|
||||||
<p>Root User: {props.root_user_email}</p>
|
try {
|
||||||
<p>Billing Contact: {props.billing_contact?.email || 'Not set'}</p>
|
const token = auth.user.access_token;
|
||||||
<h3>Questionnaire</h3>
|
const requestOptions = {
|
||||||
<p>Q1: {props.intake_questionnaire?.question_one || 'Not set'}</p>
|
method: "DELETE",
|
||||||
<p>Q2: {props.intake_questionnaire?.question_two || 'Not set'}</p>
|
headers: {
|
||||||
<p>Q3: {props.intake_questionnaire?.question_three || 'Not set'}</p>
|
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)}>
|
return (
|
||||||
Remove yourself from organisation
|
<Space vertical size={16}>
|
||||||
</Button>
|
<Card
|
||||||
<Button type="primary" danger onClick={() => removeOrg(props.organisation_id)}>
|
title={props.name}
|
||||||
Remove organisation
|
extra={<a href="#">More</a>}
|
||||||
</Button>
|
style={{ width: 300 }}
|
||||||
</Card>
|
>
|
||||||
</Space>
|
<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;
|
export default OrgPanel;
|
||||||
|
|
@ -1,48 +1,50 @@
|
||||||
import React, {useState} from "react";
|
import React, { useState } from "react";
|
||||||
import OrgPanel from "./OrgPanel";
|
import OrgPanel from "./OrgPanel";
|
||||||
import {useAuth} from "react-oidc-context";
|
import { useAuth } from "react-oidc-context";
|
||||||
import type {OrgObject} from "./hooks/OrgContext.ts";
|
import type { OrgObject } from "./hooks/OrgContext.ts";
|
||||||
import type { JSX } from "react/jsx-runtime";
|
import type { JSX } from "react/jsx-runtime";
|
||||||
import {useRefreshContext} from "./hooks/RefreshContext.ts";
|
import { useRefreshContext } from "./hooks/RefreshContext.ts";
|
||||||
|
|
||||||
const Organisations: React.FC = () => {
|
const Organisations: React.FC = () => {
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const [orgData, setCurrentOrgData] = useState<OrgObject[]>();
|
const [orgData, setCurrentOrgData] = useState<OrgObject[]>();
|
||||||
const { refreshKey } = useRefreshContext();
|
const { refreshKey } = useRefreshContext();
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchOrgData = async () => {
|
const fetchOrgData = async () => {
|
||||||
console.log("Fetching org data");
|
console.log("Fetching org data");
|
||||||
if (!auth.user) return
|
if (!auth.user) return;
|
||||||
try {
|
try {
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${auth.user.access_token}`,
|
Authorization: `Bearer ${auth.user.access_token}`,
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
const response = await fetch("/api/v1/user/self/orgs", requestOptions);
|
const response = await fetch("/api/v1/user/self/orgs", requestOptions);
|
||||||
const orgsObject = await response.json();
|
const orgsObject = await response.json();
|
||||||
setCurrentOrgData(orgsObject['organisations'])
|
setCurrentOrgData(orgsObject["organisations"]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchOrgData().catch(console.error);
|
||||||
|
}, [auth.user, refreshKey]);
|
||||||
|
|
||||||
} catch (e) {
|
console.log(orgData);
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchOrgData()
|
|
||||||
.catch(console.error);
|
|
||||||
}, [auth.user, refreshKey]);
|
|
||||||
|
|
||||||
console.log(orgData);
|
let panels: JSX.Element[] = [];
|
||||||
|
if (orgData) {
|
||||||
let panels: JSX.Element[] = [];
|
panels = orgData.map((org) => (
|
||||||
if (orgData) {
|
<OrgPanel {...org} key={org.organisation_id} />
|
||||||
panels = orgData.map(org => <OrgPanel {...org} key={org.organisation_id} />);
|
));
|
||||||
}
|
}
|
||||||
return(<>
|
return (
|
||||||
<h1>Your organisations</h1>
|
<>
|
||||||
{panels}
|
<h1>Your organisations</h1>
|
||||||
</>);
|
{panels}
|
||||||
}
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Organisations;
|
export default Organisations;
|
||||||
|
|
@ -1,23 +1,25 @@
|
||||||
// src/Posts.jsx
|
// src/Posts.jsx
|
||||||
|
|
||||||
import { useUserContext} from './hooks/UserContext.ts';
|
import { useUserContext } from "./hooks/UserContext.ts";
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const { currentUser } = useUserContext();
|
const { currentUser } = useUserContext();
|
||||||
|
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
console.log(currentUser);
|
console.log(currentUser);
|
||||||
console.log(currentUser.organisations);
|
console.log(currentUser.organisations);
|
||||||
const userOrgs: { name: string, id: number }[] = currentUser.organisations;
|
const userOrgs: { name: string; id: number }[] = currentUser.organisations;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>User: {currentUser.first_name} {currentUser.last_name}</p>
|
<p>
|
||||||
<p>Email: {currentUser.email}</p>
|
User: {currentUser.first_name} {currentUser.last_name}
|
||||||
{userOrgs[0] && <p>Current Orgs: {userOrgs[0].name}</p>}
|
</p>
|
||||||
</>
|
<p>Email: {currentUser.email}</p>
|
||||||
);
|
{userOrgs[0] && <p>Current Orgs: {userOrgs[0].name}</p>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Profile;
|
export default Profile;
|
||||||
|
|
@ -1,36 +1,36 @@
|
||||||
import React, {createContext, useContext} from 'react';
|
import React, { createContext, useContext } from "react";
|
||||||
|
|
||||||
export interface Contact {
|
export interface Contact {
|
||||||
email: string;
|
email: string;
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrgObject {
|
export interface OrgObject {
|
||||||
name: string,
|
name: string;
|
||||||
status: "partial",
|
status: "partial";
|
||||||
root_user_email: string,
|
root_user_email: string;
|
||||||
billing_contact: Contact,
|
billing_contact: Contact;
|
||||||
owner_contact: Contact,
|
owner_contact: Contact;
|
||||||
security_contact:Contact,
|
security_contact: Contact;
|
||||||
organisation_id: number,
|
organisation_id: number;
|
||||||
intake_questionnaire: {
|
intake_questionnaire: {
|
||||||
question_one: string,
|
question_one: string;
|
||||||
question_two: string,
|
question_two: string;
|
||||||
question_three: string
|
question_three: string;
|
||||||
},
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrgContextType {
|
export interface OrgContextType {
|
||||||
currentOrg: OrgObject;
|
currentOrg: OrgObject;
|
||||||
setCurrentOrg: React.Dispatch<React.SetStateAction<OrgObject>>;
|
setCurrentOrg: React.Dispatch<React.SetStateAction<OrgObject>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OrgContext = createContext<OrgContextType | undefined>(undefined);
|
export const OrgContext = createContext<OrgContextType | undefined>(undefined);
|
||||||
|
|
||||||
export const useOrgContext = () => {
|
export const useOrgContext = () => {
|
||||||
const context: OrgContextType | undefined = useContext(OrgContext);
|
const context: OrgContextType | undefined = useContext(OrgContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
throw new Error('Organisation context not found');
|
throw new Error("Organisation context not found");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import React, {createContext, useContext} from 'react';
|
import React, { createContext, useContext } from "react";
|
||||||
|
|
||||||
export interface RefreshType {
|
export interface RefreshType {
|
||||||
refreshKey: number;
|
refreshKey: number;
|
||||||
setRefreshKey: React.Dispatch<React.SetStateAction<number>>;
|
setRefreshKey: React.Dispatch<React.SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RefreshContext = createContext<RefreshType>({
|
export const RefreshContext = createContext<RefreshType>({
|
||||||
refreshKey: 1,
|
refreshKey: 1,
|
||||||
setRefreshKey: () => {},
|
setRefreshKey: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useRefreshContext = () => {
|
export const useRefreshContext = () => {
|
||||||
const context: RefreshType | undefined = useContext(RefreshContext);
|
const context: RefreshType | undefined = useContext(RefreshContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
throw new Error('Organisation refresh context not found');
|
throw new Error("Organisation refresh context not found");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
@ -1,27 +1,26 @@
|
||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
|
||||||
export interface userObject {
|
export interface userObject {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
organisations: { name: string, id: number }[];
|
organisations: { name: string; id: number }[];
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserContextType {
|
export interface UserContextType {
|
||||||
currentUser: userObject;
|
currentUser: userObject;
|
||||||
setCurrentUser: React.Dispatch<React.SetStateAction<userObject>>;
|
setCurrentUser: React.Dispatch<React.SetStateAction<userObject>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserContext = createContext<UserContextType | undefined>(undefined);
|
export const UserContext = createContext<UserContextType | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
export const useUserContext = () => {
|
export const useUserContext = () => {
|
||||||
const context: UserContextType | undefined = useContext(UserContext);
|
const context: UserContextType | undefined = useContext(UserContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
throw new Error('User context not found!');
|
throw new Error("User context not found!");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
44
src/main.tsx
44
src/main.tsx
|
|
@ -1,27 +1,29 @@
|
||||||
import {StrictMode} from 'react'
|
import { StrictMode } from "react";
|
||||||
import {createRoot} from 'react-dom/client'
|
import { createRoot } from "react-dom/client";
|
||||||
import './index.css'
|
import "./index.css";
|
||||||
import App from './App.tsx'
|
import App from "./App.tsx";
|
||||||
import {AuthProvider, type AuthProviderProps} from "react-oidc-context";
|
import { AuthProvider, type AuthProviderProps } from "react-oidc-context";
|
||||||
import {type User, WebStorageStateStore} from "oidc-client-ts";
|
import { type User, WebStorageStateStore } from "oidc-client-ts";
|
||||||
import {BrowserRouter} from "react-router";
|
import { BrowserRouter } from "react-router";
|
||||||
|
|
||||||
const onSigninCallback = (_user: User | void): void => {
|
const onSigninCallback = (_user: User | void): void => {
|
||||||
window.history.replaceState({}, document.title, window.location.pathname)
|
window.history.replaceState({}, document.title, window.location.pathname);
|
||||||
}
|
|
||||||
|
|
||||||
const oidcConfig: AuthProviderProps = {
|
|
||||||
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>
|
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}>
|
<AuthProvider {...oidcConfig}>
|
||||||
<BrowserRouter basename="/ui">
|
<BrowserRouter basename="/ui">
|
||||||
<App/>
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</StrictMode>,)
|
</StrictMode>,
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: '/ui/',
|
base: "/ui/",
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
"/api": {
|
||||||
target: 'http://localhost:8000',
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
ws: true,
|
ws: true,
|
||||||
configure: (proxy, _options) => {
|
configure: (proxy, _options) => {
|
||||||
proxy.on('error', err => {
|
proxy.on("error", (err) => {
|
||||||
console.error(err.message)
|
console.error(err.message);
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue