Skip to content

Commit 3f9ad25

Browse files
authored
feat(frontend): request HOT's ODK server workflow (#2595)
* fix(config): fix email * refactor(organization): request HOT's ODK server workflow * refactor(createEditOrganizationForm): remove commented code * refactor(organisationService): params add * feat(oranizationForm): radiobutton to display requested odk central type, skeleton loader add * feat(createEditOrganizationForm): options to allow users edit org odk creds * refactor(organizationDetailsValidation): update validations to allow user edit and override ODK creds during org edit * refactor(projectDetailsForm): update label
1 parent 94aa651 commit 3f9ad25

File tree

6 files changed

+106
-45
lines changed

6 files changed

+106
-45
lines changed

src/frontend/src/api/OrganisationService.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,15 @@ export const PatchOrganizationDataService = (url: string, payload: any) => {
187187
};
188188
};
189189

190-
export const ApproveOrganizationService = (url: string) => {
190+
export const ApproveOrganizationService = (
191+
url: string,
192+
params: { org_id: number; set_primary_org_odk_server: boolean },
193+
) => {
191194
return async (dispatch: AppDispatch) => {
192195
const approveOrganization = async (url: string) => {
193196
try {
194197
dispatch(OrganisationAction.SetOrganizationApproving(true));
195-
await axios.post(url);
198+
await axios.post(url, {}, { params });
196199
dispatch(
197200
CommonActions.SetSnackBar({
198201
message: 'Organization approved successfully.',

src/frontend/src/components/ApproveOrganization/OrganizationForm.tsx

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ import {
1010
} from '@/api/OrganisationService';
1111
import { OrganisationAction } from '@/store/slices/organisationSlice';
1212
import { useAppDispatch, useAppSelector } from '@/types/reduxTypes';
13+
import RadioButton from '@/components/common/RadioButton';
14+
import { radioOptionsType } from '@/models/organisation/organisationModel';
15+
import FormFieldSkeletonLoader from '@/components/Skeletons/common/FormFieldSkeleton';
16+
17+
const VITE_API_URL = import.meta.env.VITE_API_URL;
18+
19+
const odkTypeOptions: radioOptionsType[] = [
20+
{ name: 'odk_server_type', value: 'OWN', label: 'Own ODK server' },
21+
{ name: 'odk_server_type', value: 'HOT', label: "HOT's ODK server" },
22+
];
1323

1424
const OrganizationForm = () => {
1525
const dispatch = useAppDispatch();
@@ -26,25 +36,27 @@ const OrganizationForm = () => {
2636
const organizationApprovalSuccess = useAppSelector(
2737
(state) => state.organisation.organizationApprovalStatus.isSuccess,
2838
);
39+
const organisationFormDataLoading = useAppSelector((state) => state.organisation.organisationFormDataLoading);
2940

3041
useEffect(() => {
3142
if (organizationId) {
32-
dispatch(GetIndividualOrganizationService(`${import.meta.env.VITE_API_URL}/organisation/${organizationId}`));
43+
dispatch(GetIndividualOrganizationService(`${VITE_API_URL}/organisation/${organizationId}`));
3344
}
3445
}, [organizationId]);
3546

3647
const approveOrganization = () => {
3748
if (organizationId) {
3849
dispatch(
39-
ApproveOrganizationService(
40-
`${import.meta.env.VITE_API_URL}/organisation/approve?org_id=${parseInt(organizationId)}`,
41-
),
50+
ApproveOrganizationService(`${VITE_API_URL}/organisation/approve`, {
51+
org_id: +organizationId,
52+
set_primary_org_odk_server: !organisationFormData?.odk_central_url,
53+
}),
4254
);
4355
}
4456
};
4557

4658
const rejectOrganization = () => {
47-
dispatch(RejectOrganizationService(`${import.meta.env.VITE_API_URL}/organisation/unapproved/${organizationId}`));
59+
dispatch(RejectOrganizationService(`${VITE_API_URL}/organisation/unapproved/${organizationId}`));
4860
};
4961

5062
// redirect to manage-organization page after approve/reject success
@@ -56,6 +68,13 @@ const OrganizationForm = () => {
5668
}
5769
}, [organizationApprovalSuccess]);
5870

71+
if (organisationFormDataLoading)
72+
return (
73+
<div className="fmtm-bg-white fmtm-p-5">
74+
<FormFieldSkeletonLoader count={8} />
75+
</div>
76+
);
77+
5978
return (
6079
<div className="fmtm-max-w-[50rem] fmtm-bg-white fmtm-py-5 lg:fmtm-py-10 fmtm-px-5 lg:fmtm-px-9 fmtm-mx-auto">
6180
<div className="fmtm-flex fmtm-justify-center">
@@ -100,15 +119,25 @@ const OrganizationForm = () => {
100119
onChange={() => {}}
101120
disabled
102121
/>
103-
<InputTextField
104-
id="odk_central_url"
105-
name="odk_central_url"
106-
label="ODK Central URL "
107-
value={organisationFormData?.odk_central_url}
108-
onChange={() => {}}
109-
fieldType="text"
110-
disabled
122+
<RadioButton
123+
topic="ODK Server Type"
124+
options={odkTypeOptions}
125+
direction="column"
126+
value={organisationFormData?.odk_central_url ? 'OWN' : 'HOT'}
127+
onChangeData={() => {}}
128+
className="fmtm-text-base fmtm-text-[#7A7676] fmtm-mt-1"
111129
/>
130+
{organisationFormData?.odk_central_url && (
131+
<InputTextField
132+
id="odk_central_url"
133+
name="odk_central_url"
134+
label="ODK Central URL "
135+
value={organisationFormData?.odk_central_url}
136+
onChange={() => {}}
137+
fieldType="text"
138+
disabled
139+
/>
140+
)}
112141
<InputTextField
113142
id="url"
114143
name="url"

src/frontend/src/components/CreateEditOrganization/CreateEditOrganizationForm.tsx

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,27 @@ import OrganizationDetailsValidation from '@/components/CreateEditOrganization/v
99
import RadioButton from '@/components/common/RadioButton';
1010
import { PatchOrganizationDataService, PostOrganisationDataService } from '@/api/OrganisationService';
1111
import { diffObject } from '@/utilfunctions/compareUtils';
12-
import { CustomCheckbox } from '@/components/common/Checkbox';
13-
import { organizationTypeOptionsType } from '@/models/organisation/organisationModel';
12+
import { radioOptionsType } from '@/models/organisation/organisationModel';
1413
import { useAppDispatch, useAppSelector } from '@/types/reduxTypes';
1514
import UploadArea from '@/components/common/UploadArea';
1615
import { CommonActions } from '@/store/slices/CommonSlice';
16+
import { CustomCheckbox } from '@/components/common/Checkbox';
1717

1818
const API_URL = import.meta.env.VITE_API_URL;
1919

20-
const organizationTypeOptions: organizationTypeOptionsType[] = [
20+
const organizationTypeOptions: radioOptionsType[] = [
2121
{ name: 'osm_community', value: 'OSM_COMMUNITY', label: 'OSM Community' },
2222
{ name: 'company', value: 'COMPANY', label: 'Company' },
2323
{ name: 'non_profit', value: 'NON_PROFIT', label: 'Non-profit' },
2424
{ name: 'university', value: 'UNIVERSITY', label: 'University' },
2525
{ name: 'other', value: 'OTHER', label: 'Other' },
2626
];
2727

28+
const odkTypeOptions: radioOptionsType[] = [
29+
{ name: 'odk_server_type', value: 'OWN', label: 'Use your own ODK server' },
30+
{ name: 'odk_server_type', value: 'HOT', label: "Request HOT's ODK server" },
31+
];
32+
2833
const CreateEditOrganizationForm = ({ organizationId }: { organizationId: string }) => {
2934
const navigate = useNavigate();
3035
const dispatch = useAppDispatch();
@@ -35,15 +40,16 @@ const CreateEditOrganizationForm = ({ organizationId }: { organizationId: string
3540

3641
const submission = () => {
3742
if (!organizationId) {
38-
const { fillODKCredentials, ...filteredValues } = values;
43+
const { ...filteredValues } = values;
44+
const request_odk_server = filteredValues.odk_server_type === 'HOT';
3945
dispatch(
40-
PostOrganisationDataService(`${API_URL}/organisation`, {
46+
PostOrganisationDataService(`${API_URL}/organisation?request_odk_server=${request_odk_server}`, {
4147
...filteredValues,
4248
logo: filteredValues.logo ? filteredValues.logo?.[0].file : null,
4349
}),
4450
);
4551
} else {
46-
const { fillODKCredentials, ...filteredValues } = values;
52+
const { ...filteredValues } = values;
4753
let changedValues = diffObject(organisationFormData, filteredValues);
4854
if (changedValues.logo) {
4955
changedValues = {
@@ -92,16 +98,12 @@ const CreateEditOrganizationForm = ({ organizationId }: { organizationId: string
9298
}, [postOrganisationData]);
9399

94100
useEffect(() => {
95-
if (!values?.fillODKCredentials) {
101+
if (values?.odk_server_type === 'HOT') {
96102
handleCustomChange('odk_central_url', '');
97103
handleCustomChange('odk_central_user', '');
98104
handleCustomChange('odk_central_password', '');
99105
}
100-
}, [values?.fillODKCredentials]);
101-
102-
useEffect(() => {
103-
handleCustomChange('fillODKCredentials', false);
104-
}, []);
106+
}, [values?.odk_server_type]);
105107

106108
useEffect(() => {
107109
if (organizationId) {
@@ -163,16 +165,30 @@ const CreateEditOrganizationForm = ({ organizationId }: { organizationId: string
163165
required
164166
errorMsg={errors.description}
165167
/>
166-
<CustomCheckbox
167-
key="fillODKCredentials"
168-
label="Fill ODK credentials now"
169-
checked={values.fillODKCredentials}
170-
onCheckedChange={() => {
171-
handleCustomChange('fillODKCredentials', !values.fillODKCredentials);
172-
}}
173-
className="fmtm-text-black"
174-
/>
175-
{values?.fillODKCredentials && (
168+
{!organizationId && (
169+
<RadioButton
170+
topic="ODK Server Type"
171+
options={odkTypeOptions}
172+
direction="column"
173+
value={values.odk_server_type}
174+
onChangeData={(value) => {
175+
handleCustomChange('odk_server_type', value);
176+
}}
177+
className="fmtm-text-base fmtm-text-[#7A7676] fmtm-mt-1"
178+
errorMsg={errors.odk_server_type}
179+
required
180+
/>
181+
)}
182+
{organizationId && (
183+
<CustomCheckbox
184+
label="Update ODK Credentials"
185+
checked={values?.update_odk_credentials}
186+
onCheckedChange={(checked) => {
187+
handleCustomChange('update_odk_credentials', checked);
188+
}}
189+
/>
190+
)}
191+
{(values?.odk_server_type === 'OWN' || values?.update_odk_credentials) && (
176192
<div className="fmtm-flex fmtm-flex-col fmtm-gap-6">
177193
<InputTextField
178194
id="odk_central_url"

src/frontend/src/components/CreateEditOrganization/validation/OrganizationDetailsValidation.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ interface OrganisationValues {
1313
odk_central_password: string;
1414
osm_profile: string;
1515
community_type: string;
16-
fillODKCredentials: boolean;
1716
associated_email: string;
17+
odk_server_type: string;
18+
update_odk_credentials?: boolean;
1819
}
1920
interface ValidationErrors {
2021
logo?: string;
@@ -27,8 +28,9 @@ interface ValidationErrors {
2728
odk_central_password?: string;
2829
osm_profile?: string;
2930
community_type?: string;
30-
fillODKCredentials?: boolean;
3131
associated_email?: string;
32+
odk_server_type?: string;
33+
update_odk_credentials?: string;
3234
}
3335

3436
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -61,19 +63,30 @@ function OrganizationDetailsValidation(values: OrganisationValues) {
6163
errors.associated_email = 'Invalid Email.';
6264
}
6365

64-
if (values?.odk_central_url && !isValidUrl(values.odk_central_url)) {
66+
if (!values?.odk_server_type && !values?.id) {
67+
errors.odk_server_type = 'ODK Server Type is Required.';
68+
}
69+
70+
if (
71+
(values?.odk_server_type === 'OWN' || values?.update_odk_credentials) &&
72+
values?.odk_central_url &&
73+
!isValidUrl(values.odk_central_url)
74+
) {
6575
errors.odk_central_url = 'Invalid URL.';
6676
}
6777

68-
if (values?.fillODKCredentials && isInputEmpty(values.odk_central_url)) {
78+
if ((values?.odk_server_type === 'OWN' || values?.update_odk_credentials) && isInputEmpty(values.odk_central_url)) {
6979
errors.odk_central_url = 'ODK Central URL is Required.';
7080
}
7181

72-
if (values?.fillODKCredentials && isInputEmpty(values.odk_central_user)) {
82+
if ((values?.odk_server_type === 'OWN' || values?.update_odk_credentials) && isInputEmpty(values.odk_central_user)) {
7383
errors.odk_central_user = 'ODK Central Email is Required.';
7484
}
7585

76-
if (values?.fillODKCredentials && isInputEmpty(values.odk_central_password)) {
86+
if (
87+
(values?.odk_server_type === 'OWN' || values?.update_odk_credentials) &&
88+
isInputEmpty(values.odk_central_password)
89+
) {
7790
errors.odk_central_password = 'ODK Central Password is Required.';
7891
}
7992

src/frontend/src/components/createnewproject/ProjectDetailsForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ const ProjectDetailsForm = ({ flag }) => {
230230
{hasODKCredentials && (
231231
<CustomCheckbox
232232
key="useDefaultODKCredentials"
233-
label="Use default ODK credentials"
233+
label="Use default or requested ODK credentials"
234234
checked={values.useDefaultODKCredentials}
235235
onCheckedChange={() => {
236236
handleCustomChange('useDefaultODKCredentials', !values.useDefaultODKCredentials);

src/frontend/src/models/organisation/organisationModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface GetOrganisationDataModel {
3333
odk_central_url: string | null;
3434
}
3535

36-
export type organizationTypeOptionsType = {
36+
export type radioOptionsType = {
3737
name: string;
3838
value: string;
3939
label: string;

0 commit comments

Comments
 (0)