Recreate the org flow
This commit is contained in:
parent
ac733af3b5
commit
49e4bd8f8a
8 changed files with 233 additions and 107 deletions
31
src/App.tsx
31
src/App.tsx
|
|
@ -19,6 +19,8 @@ import type {userObject} from "./hooks/UserContext.ts";
|
|||
import Organisations from "./Organisations.tsx";
|
||||
import {RefreshContext} from "./hooks/RefreshContext.ts";
|
||||
import Bridges from "./Bridges.tsx";
|
||||
import {OrgContext, type OrgContextType, type OrgObject} from "./hooks/OrgContext.ts";
|
||||
import CreateOrgFlow from "./CreateOrgFlow.tsx";
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
|
||||
|
|
@ -72,9 +74,28 @@ const App: React.FC = () => {
|
|||
},
|
||||
setCurrentUser: () => {}
|
||||
}
|
||||
|
||||
const defaultOrg: OrgContextType = {
|
||||
currentOrg: {
|
||||
name: "",
|
||||
status: "partial",
|
||||
root_user_email: "",
|
||||
billing_contact: {email: "", id: -1},
|
||||
owner_contact: {email: "", id: -1},
|
||||
security_contact: {email: "", id: -1},
|
||||
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);
|
||||
|
||||
|
||||
/*****************************
|
||||
|
|
@ -132,7 +153,7 @@ const App: React.FC = () => {
|
|||
navigate('/profile');
|
||||
break;
|
||||
case 'orgs':
|
||||
navigate('/organisations');
|
||||
navigate('/create-org-flow');
|
||||
break;
|
||||
case 'logout':
|
||||
auth.removeUser().then(() => navigate('/profile'));
|
||||
|
|
@ -154,7 +175,7 @@ const App: React.FC = () => {
|
|||
|
||||
if (currentUser.organisations.length > 0) {
|
||||
|
||||
side_nav_items = [ getItem('My organisations', 'orgs', <SettingOutlined />),
|
||||
side_nav_items = [ getItem('Onboarding', 'orgs', <SettingOutlined />),
|
||||
getItem('Bridges', 'bridges', <DeploymentUnitOutlined />), ];
|
||||
|
||||
const currentOrg = currentUser.organisations[0]['name']
|
||||
|
|
@ -163,6 +184,7 @@ const App: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<OrgContext.Provider value={{ currentOrg, setCurrentOrg }}>
|
||||
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
|
||||
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
|
||||
<Layout>
|
||||
|
|
@ -209,6 +231,8 @@ const App: React.FC = () => {
|
|||
<Route path="/" element={<Home/>}/>
|
||||
{currentUser && <Route path="/profile" element={<Profile/>}/> }
|
||||
{currentUser && <Route path="/organisations" element={<Organisations/>}/> }
|
||||
{currentUser && <Route path="/create-org-flow" element={<CreateOrgFlow/>}/> }
|
||||
|
||||
{currentUser && <Route path="/bridges" element={<Bridges/>}/> }
|
||||
|
||||
</Routes>
|
||||
|
|
@ -218,6 +242,7 @@ const App: React.FC = () => {
|
|||
</Layout>
|
||||
</RefreshContext.Provider>
|
||||
</UserContext.Provider>
|
||||
</OrgContext.Provider>
|
||||
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ 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;
|
||||
import { QRCode } from 'antd';
|
||||
interface BridgeDataType {
|
||||
fingerprint: string;
|
||||
bridgeline: string;
|
||||
|
|
@ -28,7 +27,6 @@ const Bridges = () => {
|
|||
|
||||
bridgeLines.forEach((line) => {line.qrCodeText = makeQRCodeText(line.bridgeline)});
|
||||
console.log(bridgeLines);
|
||||
const { token } = useToken();
|
||||
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [searchedColumn, setSearchedColumn] = useState('');
|
||||
|
|
@ -54,7 +52,7 @@ const Bridges = () => {
|
|||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
|
||||
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
|
||||
<Input
|
||||
ref={searchInput}
|
||||
//ref={searchInput}
|
||||
placeholder={`Search ${dataIndex}`}
|
||||
value={selectedKeys[0]}
|
||||
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
|
|
@ -146,7 +144,7 @@ const Bridges = () => {
|
|||
width: '40%',
|
||||
key: 'qrCodeText',
|
||||
dataIndex: 'qrCodeText',
|
||||
render: (text, record) => {return (<QRCode value={record.qrCodeText} />)}
|
||||
render: (_text, record) => {return (<QRCode value={record.qrCodeText} />)}
|
||||
},
|
||||
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
// src/Posts.jsx
|
||||
|
||||
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 {
|
||||
const [name, q1, q2, q3] = Object.values(values);
|
||||
const data = {
|
||||
"name": name,
|
||||
"intake_questionnaire": {
|
||||
"question_one": q1,
|
||||
"question_two": q2,
|
||||
"question_three": q3,
|
||||
}
|
||||
}
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'}
|
||||
}
|
||||
const response = await fetch("/api/v1/org", requestOptions);
|
||||
await response.json();
|
||||
setRefreshKey(prev => prev + 1);
|
||||
console.log(refreshKey);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const onFinish = (values: FormData) => {
|
||||
console.log(values);
|
||||
submitOrgData(values)
|
||||
};
|
||||
|
||||
|
||||
if (!currentUser) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default CreateOrg;
|
||||
187
src/CreateOrgFlow.tsx
Normal file
187
src/CreateOrgFlow.tsx
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// src/Posts.jsx
|
||||
|
||||
import {useUserContext} from './hooks/UserContext.ts';
|
||||
import { Button, Form, Input } from 'antd';
|
||||
//import React, {useEffect, useState} from "react";
|
||||
import {useAuth} from "react-oidc-context";
|
||||
import {RefreshContext} from "./hooks/RefreshContext.ts";
|
||||
import {useContext, useState} from "react";
|
||||
import {useOrgContext} from "./hooks/OrgContext.ts";
|
||||
|
||||
const CreateOrgStep1 = () => {
|
||||
const {currentUser} = useUserContext();
|
||||
const [form] = Form.useForm();
|
||||
const auth = useAuth();
|
||||
const {refreshKey, setRefreshKey} = useContext(RefreshContext);
|
||||
const {currentOrg, setCurrentOrg} = useOrgContext();
|
||||
console.log('context value:', {currentOrg, setCurrentOrg});
|
||||
|
||||
const {TextArea} = Input;
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
|
||||
async function submitOrgBasicData(values: FormData) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const [name, q1, q2] = Object.values(values);
|
||||
const data = {
|
||||
"name": name,
|
||||
"intake_questionnaire": {
|
||||
"question_one": q1,
|
||||
"question_two": q2,
|
||||
}
|
||||
}
|
||||
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
const response_org = await fetch("/api/v1/org", requestOptions);
|
||||
const orgID = await response_org.json();
|
||||
const response_currentOrg = await fetch("/api/v1/org?org_id=" + orgID['id'], {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const currentOrgData = await response_currentOrg.json()
|
||||
setCurrentOrg(currentOrgData['organisations'][0]);
|
||||
setCurrentStep(currentStep + 1);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitOrgBillingData(values: FormData) {
|
||||
if (!auth.user) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const [first_name, last_name, email, phone_number, vat_number, street_address, street_address_line_2, locality] = Object.values(values);
|
||||
const data = {
|
||||
"organisation_id": currentOrg.organisation_id,
|
||||
"contact_type": "billing",
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"email": email,
|
||||
"phone_number": phone_number,
|
||||
"vat_number": vat_number,
|
||||
"street_address": street_address,
|
||||
"street_address_line_2": street_address_line_2,
|
||||
"locality": locality,
|
||||
}
|
||||
|
||||
const token = auth.user.access_token;
|
||||
const requestOptions = {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
await fetch("/api/v1/org/contact", requestOptions);
|
||||
setCurrentStep(currentStep + 1);
|
||||
setRefreshKey(refreshKey +1);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentUser) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<>
|
||||
<h2>Step 1 - Basic info</h2>
|
||||
<Form
|
||||
layout="vertical"
|
||||
form={form}
|
||||
onFinish={submitOrgBasicData}
|
||||
>
|
||||
<Form.Item name="Organisation name" label="Organisation name" rules={[{required: true}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<h4>Intake Questionnaire</h4>
|
||||
<Form.Item name="Question 1" label="What region(s) does your organisation operate in?">
|
||||
<TextArea showCount maxLength={100} placeholder="e.g., Southern Africa"/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 2" label="Briefly describe how your organisation supports civil society.">
|
||||
<TextArea showCount maxLength={300}
|
||||
placeholder="e.g., Our organisation supports various groups such as ..."/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Question 3" label="Name two references/contacts within the community, and briefly describe the nature of your relationsip and projects you've worked on together.">
|
||||
<TextArea showCount maxLength={500}
|
||||
placeholder="e.g., I worked with SR2 Communications on providing digital security training from 2023-2025."/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentStep === 2) {
|
||||
return <>
|
||||
<h2>Step 2 - Contact and billing details</h2>
|
||||
|
||||
<Form
|
||||
layout="vertical"
|
||||
form={form}
|
||||
onFinish={submitOrgBillingData}
|
||||
>
|
||||
<Form.Item name="Billing contact first name" label="Billing contact first name"
|
||||
rules={[{required: true}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Billing contact last name" label="Billing contact last name"
|
||||
rules={[{required: true}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Billing contact email" label="Billing contact email address"
|
||||
rules={[{required: true, type: "email", message: "Not a valid e-mail address."}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Billing contact phone" label="Billing contact phone number">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Billing contact VAT number" label="Billing contact VAT number">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Billing address line 1" label="Billing address line 1" rules={[{required: true}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item name="Billing address line 2" label="Billing address line 2">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item name="locality" label="City" rules={[{required: true}]}>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</>
|
||||
}
|
||||
|
||||
if (currentStep === 3) {
|
||||
return <> <h2>Thank you for submitting your organisation info. </h2>
|
||||
<p> This will be manually reviewed by a member of SR2 Cloud and you will be contacted shortly.
|
||||
We aim to complete reviews within 24 hours. You can change your answers at any point from the
|
||||
'My organisations' section.</p>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export default CreateOrgStep1;
|
||||
|
|
@ -3,7 +3,6 @@ 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 = () => {
|
||||
|
|
@ -43,8 +42,6 @@ const Organisations: React.FC = () => {
|
|||
return(<>
|
||||
<h1>Your organisations</h1>
|
||||
{panels}
|
||||
<h1>Add a new organisation</h1>
|
||||
<CreateOrg></CreateOrg>
|
||||
</>);
|
||||
}
|
||||
|
||||
|
|
|
|||
19
src/home.tsx
19
src/home.tsx
|
|
@ -1,20 +1,23 @@
|
|||
import {Breadcrumb, theme} from "antd";
|
||||
import {Breadcrumb, Button, theme} from "antd";
|
||||
import React from "react";
|
||||
//import {useOrgContext} from './OrgContext';
|
||||
import CreateOrg from "./CreateOrg.tsx";
|
||||
import {useUserContext} from "./hooks/UserContext.ts";
|
||||
import {useNavigate} from "react-router";
|
||||
import Organisations from "./Organisations.tsx";
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const {
|
||||
token: { colorBgContainer, borderRadiusLG },
|
||||
} = theme.useToken();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { currentUser } = useUserContext();
|
||||
console.log(currentUser.organisations);
|
||||
if (currentUser.organisations.length === 0) {
|
||||
return <>
|
||||
<h3>Your user has no organisations yet. Create one here.</h3>
|
||||
<CreateOrg/>
|
||||
<h2>Welcome to SR2 Cloud! </h2>
|
||||
<h3>If your organisation already uses SR2 Cloud, please ask your administrator to add you to the organisation.</h3>
|
||||
<p> If your organanisation is new to SR2 Cloud, start the onboarding process </p>
|
||||
|
||||
<Button type="primary" onClick={() => navigate('/organisations')}> Start onboarding </Button>
|
||||
</>
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +33,9 @@ const Home: React.FC = () => {
|
|||
}}
|
||||
>
|
||||
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
|
||||
|
||||
<div>
|
||||
<Organisations/>
|
||||
</div>
|
||||
</div>
|
||||
</>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ export interface OrgObject {
|
|||
status: "partial",
|
||||
root_user_email: string,
|
||||
billing_contact: Contact,
|
||||
owner_contact: Conotact,
|
||||
security_contact:Cntact,
|
||||
owner_contact: Contact,
|
||||
security_contact:Contact,
|
||||
organisation_id: number,
|
||||
intake_questionnaire: {
|
||||
question_one: string,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const Profile = () => {
|
|||
<>
|
||||
<p>User: {currentUser.first_name} {currentUser.last_name}</p>
|
||||
<p>Email: {currentUser.email}</p>
|
||||
<p>Current Orgs: {userOrgs[0].name}</p>
|
||||
{userOrgs[0] && <p>Current Orgs: {userOrgs[0].name}</p>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue