Add an initial organisation flow
This commit is contained in:
parent
3bd1a5007a
commit
ac733af3b5
12 changed files with 432 additions and 99 deletions
|
|
@ -22,12 +22,14 @@
|
|||
"@types/node": "^24.12.3",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-highlight-words": "^0.20.1",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"eslint": "^10.3.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.6.0",
|
||||
"react-highlight-words": "^0.21.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vite": "^8.0.12"
|
||||
|
|
|
|||
69
src/App.tsx
69
src/App.tsx
|
|
@ -5,19 +5,20 @@ 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';
|
||||
import Home from "./Home.tsx";
|
||||
import {Route, Routes, useLocation, useNavigate} from "react-router";
|
||||
import Profile from "./Profile.tsx";
|
||||
import {type UserContextType} from './UserContext.tsx';
|
||||
import {UserContext} from './UserContext.tsx';
|
||||
import type {userObject} from "./UserContext.tsx";
|
||||
import {type UserContextType} from './hooks/UserContext.ts';
|
||||
import {UserContext} from './hooks/UserContext.ts';
|
||||
import type {userObject} from "./hooks/UserContext.ts";
|
||||
|
||||
import {type OrgContextType} from './OrgContext.tsx';
|
||||
import {OrgContext} from './OrgContext.tsx';
|
||||
import type {orgObject} from "./OrgContext.tsx";
|
||||
import Organisations from "./Organisations.tsx";
|
||||
import {RefreshContext} from "./hooks/RefreshContext.ts";
|
||||
import Bridges from "./Bridges.tsx";
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
|
||||
|
|
@ -66,28 +67,16 @@ const App: React.FC = () => {
|
|||
first_name: "",
|
||||
last_name: "",
|
||||
email: "",
|
||||
organisations: []
|
||||
organisations: [],
|
||||
id: -1
|
||||
},
|
||||
setCurrentUser: () => {}
|
||||
}
|
||||
|
||||
const defaultOrg: OrgContextType = {
|
||||
currentOrg: {
|
||||
billing_contact: "",
|
||||
owner_contact: "",
|
||||
name: "",
|
||||
status: "",
|
||||
root_user: "",
|
||||
security_contact: "",
|
||||
},
|
||||
setCurrentOrg: () => {}
|
||||
}
|
||||
|
||||
const [currentUser, setCurrentUser] = useState<userObject>(defaultUser.currentUser);
|
||||
const [currentOrg, setCurrentOrg] = useState<orgObject>(defaultOrg.currentOrg);
|
||||
const [refreshKey, setRefreshKey] = useState<number>(1);
|
||||
|
||||
|
||||
|
||||
// STRILL NEEDS SOMETHING TO get CURRENT USER ORGS and map first one to a CURRENT ORG fetched from the API
|
||||
// STILL NEEDS THE WRAPPER IN THE RETURN STATEMETNS
|
||||
/*****************************
|
||||
GETTING CURRENT USER FROM API
|
||||
*****************************/
|
||||
|
|
@ -108,7 +97,7 @@ const App: React.FC = () => {
|
|||
console.error(e);
|
||||
}
|
||||
})();
|
||||
}, [auth]);
|
||||
}, [auth, refreshKey]);
|
||||
|
||||
|
||||
if (!currentUser) {
|
||||
|
|
@ -142,8 +131,14 @@ const App: React.FC = () => {
|
|||
case 'profile':
|
||||
navigate('/profile');
|
||||
break;
|
||||
case 'orgs':
|
||||
navigate('/organisations');
|
||||
break;
|
||||
case 'logout':
|
||||
auth.removeUser().then(r => navigate('/profile'));
|
||||
auth.removeUser().then(() => navigate('/profile'));
|
||||
break;
|
||||
case 'bridges':
|
||||
navigate('/bridges');
|
||||
break;
|
||||
default:
|
||||
console.log('Clicked item:', e.key);
|
||||
|
|
@ -151,19 +146,26 @@ const App: React.FC = () => {
|
|||
};
|
||||
|
||||
|
||||
//const organisations: [] = current_user.organisations;
|
||||
|
||||
const side_nav_items: MenuItem[] = currentUser.organisations && [
|
||||
getItem('Files', '9', <SettingOutlined />),
|
||||
];
|
||||
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("Organisation Settings", 'orgsettings', <SettingOutlined />),
|
||||
];
|
||||
|
||||
if (currentUser.organisations.length > 0) {
|
||||
|
||||
side_nav_items = [ getItem('My organisations', 'orgs', <SettingOutlined />),
|
||||
getItem('Bridges', 'bridges', <DeploymentUnitOutlined />), ];
|
||||
|
||||
const currentOrg = currentUser.organisations[0]['name']
|
||||
top_nav_items.push(getItem(currentOrg +" Settings", 'orgsettings', <SettingOutlined />));
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||
<Layout>
|
||||
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
|
||||
<Layout>
|
||||
<Header style={{ display: 'flex', alignItems: 'right' }}>
|
||||
<div className="demo-logo" >
|
||||
<h1>SR22</h1>
|
||||
|
|
@ -206,12 +208,15 @@ const App: React.FC = () => {
|
|||
<Routes>
|
||||
<Route path="/" element={<Home/>}/>
|
||||
{currentUser && <Route path="/profile" element={<Profile/>}/> }
|
||||
{currentUser && <Route path="/organisations" element={<Organisations/>}/> }
|
||||
{currentUser && <Route path="/bridges" element={<Bridges/>}/> }
|
||||
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</RefreshContext.Provider>
|
||||
</UserContext.Provider>
|
||||
|
||||
);
|
||||
|
|
|
|||
163
src/Bridges.tsx
Normal file
163
src/Bridges.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
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, theme } from 'antd';
|
||||
const { useToken } = theme;
|
||||
interface BridgeDataType {
|
||||
fingerprint: string;
|
||||
bridgeline: string;
|
||||
qrCodeText: string;
|
||||
}
|
||||
|
||||
const Bridges = () => {
|
||||
|
||||
const bridgeLines: BridgeDataType[] = [
|
||||
{fingerprint: '0C6DDE3F9FC377A5E69DC044E81C857277148D71', bridgeline: "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0", qrCodeText: "" },
|
||||
{fingerprint:'8DFC222D2295A909B34B3AAAA584648EE6FAF14D', bridgeline: "obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0", qrCodeText: "" },
|
||||
{fingerprint: '0C6DDE3F9FC377A5E69DC044E81C857277148D71', bridgeline: "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0", qrCodeText: ""},
|
||||
{fingerprint:'8DFC222D2295A909B34B3AAAA584648EE6FAF14D', bridgeline: "obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0", qrCodeText: ""},
|
||||
];
|
||||
|
||||
|
||||
function makeQRCodeText(bridgeline: string) {
|
||||
return '["' + bridgeline + '"]';
|
||||
}
|
||||
|
||||
bridgeLines.forEach((line) => {line.qrCodeText = makeQRCodeText(line.bridgeline)});
|
||||
console.log(bridgeLines);
|
||||
const { token } = useToken();
|
||||
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [searchedColumn, setSearchedColumn] = useState('');
|
||||
const searchInput = useRef<InputRef>(null);
|
||||
|
||||
const handleSearch = (
|
||||
selectedKeys: string[],
|
||||
confirm: FilterDropdownProps['confirm'],
|
||||
dataIndex: DataIndex,
|
||||
) => {
|
||||
confirm();
|
||||
setSearchText(selectedKeys[0]);
|
||||
setSearchedColumn(dataIndex);
|
||||
};
|
||||
|
||||
const handleReset = (clearFilters: () => void) => {
|
||||
clearFilters();
|
||||
setSearchText('');
|
||||
};
|
||||
|
||||
type DataIndex = keyof BridgeDataType;
|
||||
const getColumnSearchProps = (dataIndex: DataIndex): TableColumnType<BridgeDataType> => ({
|
||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
|
||||
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
|
||||
<Input
|
||||
ref={searchInput}
|
||||
placeholder={`Search ${dataIndex}`}
|
||||
value={selectedKeys[0]}
|
||||
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onPressEnter={() => handleSearch(selectedKeys as string[], confirm, dataIndex)}
|
||||
style={{ marginBottom: 8, display: 'block' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => handleSearch(selectedKeys as string[], confirm, dataIndex)}
|
||||
icon={<SearchOutlined />}
|
||||
size="small"
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => clearFilters && handleReset(clearFilters)}
|
||||
size="small"
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
confirm({ closeDropdown: false });
|
||||
setSearchText((selectedKeys as string[])[0]);
|
||||
setSearchedColumn(dataIndex);
|
||||
}}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
close();
|
||||
}}
|
||||
>
|
||||
close
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: (filtered: boolean) => (
|
||||
<SearchOutlined style={{ color: filtered ? '#1677ff' : undefined }} />
|
||||
),
|
||||
onFilter: (value, record) =>
|
||||
record[dataIndex]
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes((value as string).toLowerCase()),
|
||||
filterDropdownProps: {
|
||||
onOpenChange(open) {
|
||||
if (open) {
|
||||
setTimeout(() => searchInput.current?.select(), 100);
|
||||
}
|
||||
},
|
||||
},
|
||||
render: (text) =>
|
||||
searchedColumn === dataIndex ? (
|
||||
<Highlighter
|
||||
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
|
||||
searchWords={[searchText]}
|
||||
autoEscape
|
||||
textToHighlight={text ? text.toString() : ''}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
),
|
||||
});
|
||||
|
||||
const 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} />)}
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
|
||||
return(
|
||||
<>
|
||||
|
||||
<Table<BridgeDataType> columns={bridgesColumns} dataSource={bridgeLines} />;
|
||||
</>);
|
||||
|
||||
}
|
||||
|
||||
export default Bridges;
|
||||
|
|
@ -1,25 +1,24 @@
|
|||
// src/Posts.jsx
|
||||
|
||||
import {useUserContext} from './UserContext';
|
||||
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} from "react";
|
||||
|
||||
const CreateOrg = () => {
|
||||
const { currentUser } = useUserContext();
|
||||
const [form] = Form.useForm();
|
||||
const auth = useAuth();
|
||||
|
||||
|
||||
const { refreshKey, setRefreshKey } = useContext(RefreshContext);
|
||||
console.log(refreshKey);
|
||||
async function submitOrgData(values: FormData) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
console.log("in here");
|
||||
const [name, q1, q2, q3] = Object.values(values);
|
||||
console.log("end here");
|
||||
|
||||
const data = {
|
||||
"name": name,
|
||||
"intake_questionnaire": {
|
||||
|
|
@ -28,16 +27,18 @@ const CreateOrg = () => {
|
|||
"question_three": q3,
|
||||
}
|
||||
}
|
||||
console.log(data)
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'}
|
||||
'Content-Type': 'application/json'}
|
||||
}
|
||||
const response = await fetch("/api/v1/org/", requestOptions);
|
||||
const response = await fetch("/api/v1/org", requestOptions);
|
||||
await response.json();
|
||||
setRefreshKey(prev => prev + 1);
|
||||
console.log(refreshKey);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
|
@ -55,29 +56,29 @@ const CreateOrg = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
layout= "vertical"
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Form.Item name="Organisation name" label="Organisation name" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 1" label="How does your organisation support civil society?">
|
||||
<Input placeholder="e.g., Our organisation supports various groups such as ..." />
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 2" label="Name your first reference">
|
||||
<Input placeholder="e.g., SR2 Communciations Limited" />
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 3" label="Name your second reference">
|
||||
<Input placeholder="e.g., SR2 Professional Services" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
<Form
|
||||
layout= "vertical"
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Form.Item name="Organisation name" label="Organisation name" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 1" label="How does your organisation support civil society?">
|
||||
<Input placeholder="e.g., Our organisation supports various groups such as ..." />
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 2" label="Name your first reference">
|
||||
<Input placeholder="e.g., SR2 Communciations Limited" />
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 3" label="Name your second reference">
|
||||
<Input placeholder="e.g., SR2 Professional Services" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
|
||||
</Form>
|
||||
</>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
interface orgObject {
|
||||
name: string;
|
||||
status: "partial",
|
||||
root_user: "string",
|
||||
owner_contact: "string",
|
||||
billing_contact: "string",
|
||||
security_contact: "string"
|
||||
}
|
||||
|
||||
export interface OrgContextType {
|
||||
currentOrg: orgObject;
|
||||
setCurrentOrg: React.Dispatch<React.SetStateAction<orgObject>>;
|
||||
}
|
||||
|
||||
export const OrgContext = createContext<OrgContextType | undefined>(undefined);
|
||||
|
||||
export const useOrgContext = () => {
|
||||
const context: OrgContextType | undefined = useContext(OrgContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('Organisation context not found');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
76
src/OrgPanel.tsx
Normal file
76
src/OrgPanel.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import {Button, Card, Space} from 'antd';
|
||||
import type {OrgObject} from "./hooks/OrgContext.ts";
|
||||
import {useUserContext} from "./hooks/UserContext.ts";
|
||||
import {useAuth} from "react-oidc-context";
|
||||
import {useContext} from "react";
|
||||
import {RefreshContext} from "./hooks/RefreshContext.ts";
|
||||
|
||||
const OrgPanel = (props: OrgObject) => {
|
||||
|
||||
const auth = useAuth();
|
||||
const { refreshKey, setRefreshKey } = useContext(RefreshContext);
|
||||
const {currentUser} = useUserContext();
|
||||
async function removeUserFromOrg(org_id: number) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'DELETE',
|
||||
headers: {Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'}
|
||||
}
|
||||
const response = await fetch("/api/v1/org/user?org_id=" + org_id + "&user_id=" + currentUser.id, requestOptions);
|
||||
await response.json();
|
||||
setRefreshKey(prev => prev + 1);
|
||||
console.log(refreshKey)
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function removeOrg(org_id: number) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'DELETE',
|
||||
headers: {Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'}
|
||||
}
|
||||
await fetch("/api/v1/org/self?org_id=" + org_id, requestOptions);
|
||||
setRefreshKey(prev => prev + 1);
|
||||
console.log(refreshKey)
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Space vertical size={16}>
|
||||
<Card title={props.name} extra={<a href="#">More</a>} style={{width: 300}}>
|
||||
<p>Validated? {props.status}</p>
|
||||
<p>Root User: {props.root_user_email}</p>
|
||||
<p>Billing Contact: {props.billing_contact?.email || 'Not set'}</p>
|
||||
<h3>Questionnaire</h3>
|
||||
<p>Q1: {props.intake_questionnaire?.question_one || 'Not set'}</p>
|
||||
<p>Q2: {props.intake_questionnaire?.question_two || 'Not set'}</p>
|
||||
<p>Q3: {props.intake_questionnaire?.question_three || 'Not set'}</p>
|
||||
|
||||
<Button type="primary" onClick={() => removeUserFromOrg(props.organisation_id)}>
|
||||
Remove yourself from organisation
|
||||
</Button>
|
||||
<Button type="primary" danger onClick={() => removeOrg(props.organisation_id)}>
|
||||
Remove organisation
|
||||
</Button>
|
||||
</Card>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
export default OrgPanel;
|
||||
51
src/Organisations.tsx
Normal file
51
src/Organisations.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React, {useState} from "react";
|
||||
import OrgPanel from "./OrgPanel";
|
||||
import {useAuth} from "react-oidc-context";
|
||||
import type {OrgObject} from "./hooks/OrgContext.ts";
|
||||
import type { JSX } from "react/jsx-runtime";
|
||||
import CreateOrg from "./CreateOrg.tsx";
|
||||
import {useRefreshContext} from "./hooks/RefreshContext.ts";
|
||||
|
||||
const Organisations: React.FC = () => {
|
||||
const auth = useAuth();
|
||||
const [orgData, setCurrentOrgData] = useState<OrgObject[]>();
|
||||
const { refreshKey } = useRefreshContext();
|
||||
React.useEffect(() => {
|
||||
const fetchOrgData = async () => {
|
||||
console.log("Fetching org data");
|
||||
if (!auth.user) return
|
||||
try {
|
||||
const requestOptions = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${auth.user.access_token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
const response = await fetch("/api/v1/user/self/orgs", requestOptions);
|
||||
const orgsObject = await response.json();
|
||||
setCurrentOrgData(orgsObject['organisations'])
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
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} />);
|
||||
}
|
||||
return(<>
|
||||
<h1>Your organisations</h1>
|
||||
{panels}
|
||||
<h1>Add a new organisation</h1>
|
||||
<CreateOrg></CreateOrg>
|
||||
</>);
|
||||
}
|
||||
|
||||
export default Organisations;
|
||||
11
src/home.tsx
11
src/home.tsx
|
|
@ -1,15 +1,14 @@
|
|||
import {Breadcrumb, theme} from "antd";
|
||||
import React, {useContext} from "react";
|
||||
import React from "react";
|
||||
//import {useOrgContext} from './OrgContext';
|
||||
import CreateOrg from "./CreateOrg.tsx";
|
||||
import {useUserContext} from "./UserContext.tsx";
|
||||
import {useUserContext} from "./hooks/UserContext.ts";
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const {
|
||||
token: { colorBgContainer, borderRadiusLG },
|
||||
} = theme.useToken();
|
||||
|
||||
//const { currentOrg } = useOrgContext();
|
||||
const { currentUser } = useUserContext();
|
||||
console.log(currentUser.organisations);
|
||||
if (currentUser.organisations.length === 0) {
|
||||
|
|
@ -19,8 +18,9 @@ const Home: React.FC = () => {
|
|||
</>
|
||||
}
|
||||
|
||||
//TODO: fix this breadcrumb
|
||||
return (<>
|
||||
<Breadcrumb style={{ margin: '16px 0' }} items={[{ title: 'User' }, { title: 'Bill' }]} />
|
||||
<Breadcrumb style={{ margin: '16px 0' }} items={[{ title: 'User' }, { title: 'Dashboard' }]} />
|
||||
<div
|
||||
style={{
|
||||
padding: 24,
|
||||
|
|
@ -29,7 +29,8 @@ const Home: React.FC = () => {
|
|||
borderRadius: borderRadiusLG,
|
||||
}}
|
||||
>
|
||||
Bill is a cat.
|
||||
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
|
||||
|
||||
</div>
|
||||
</>)
|
||||
}
|
||||
|
|
|
|||
36
src/hooks/OrgContext.ts
Normal file
36
src/hooks/OrgContext.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import React, {createContext, useContext} from 'react';
|
||||
|
||||
export interface Contact {
|
||||
email: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface OrgObject {
|
||||
name: string,
|
||||
status: "partial",
|
||||
root_user_email: string,
|
||||
billing_contact: Contact,
|
||||
owner_contact: Conotact,
|
||||
security_contact:Cntact,
|
||||
organisation_id: number,
|
||||
intake_questionnaire: {
|
||||
question_one: string,
|
||||
question_two: string,
|
||||
question_three: string
|
||||
},
|
||||
}
|
||||
|
||||
export interface OrgContextType {
|
||||
currentOrg: OrgObject;
|
||||
setCurrentOrg: React.Dispatch<React.SetStateAction<OrgObject>>;
|
||||
}
|
||||
|
||||
export const OrgContext = createContext<OrgContextType | undefined>(undefined);
|
||||
|
||||
export const useOrgContext = () => {
|
||||
const context: OrgContextType | undefined = useContext(OrgContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('Organisation context not found');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
19
src/hooks/RefreshContext.ts
Normal file
19
src/hooks/RefreshContext.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React, {createContext, useContext} from 'react';
|
||||
|
||||
export interface RefreshType {
|
||||
refreshKey: number;
|
||||
setRefreshKey: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const RefreshContext = createContext<RefreshType>({
|
||||
refreshKey: 1,
|
||||
setRefreshKey: () => {},
|
||||
});
|
||||
|
||||
export const useRefreshContext = () => {
|
||||
const context: RefreshType | undefined = useContext(RefreshContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('Organisation refresh context not found');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -5,7 +5,8 @@ export interface userObject {
|
|||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
organisations: string[];
|
||||
organisations: { name: string, id: number }[];
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface UserContextType {
|
||||
|
|
@ -22,3 +23,5 @@ export const useUserContext = () => {
|
|||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// src/Posts.jsx
|
||||
|
||||
import {useUserContext} from './UserContext';
|
||||
import { useUserContext} from './hooks/UserContext.ts';
|
||||
|
||||
const Profile = () => {
|
||||
const { currentUser } = useUserContext();
|
||||
|
|
@ -8,14 +8,15 @@ const Profile = () => {
|
|||
if (!currentUser) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
console.log(currentUser);
|
||||
console.log(currentUser.organisations);
|
||||
const userOrgs: { name: string, id: number }[] = currentUser.organisations;
|
||||
return (
|
||||
<>
|
||||
<p>User: {currentUser.first_name} {currentUser.last_name}</p>
|
||||
<p>Email: {currentUser.email}</p>
|
||||
<p>Orgs: {currentUser.organisations}</p>
|
||||
<p>Current Orgs: {userOrgs[0].name}</p>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue