Skip to content

Commit d02f67c

Browse files
authored
Merge pull request #39 from GTBitsOfGood/davidpang/update-partner-details
Update partner details mutation to user schema
2 parents 1e16bce + d9fcf9f commit d02f67c

File tree

5 files changed

+531
-130
lines changed

5 files changed

+531
-130
lines changed

src/app/api/invites/route.test.ts

+234-2
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ test("test email html", async () => {
131131
expect(sendEmailMock).toHaveBeenCalledWith(
132132
expect.any(String),
133133
expect.any(String),
134-
expect.stringMatching(new RegExp(`register\\?token=${mockToken}`))
134+
expect.stringMatching(new RegExp(`register\\?token=${mockToken}`)),
135135
);
136136
},
137137
});
@@ -160,7 +160,7 @@ test("UserInvite expires in one day", async () => {
160160

161161
expect(expirationDate.getTime() - currentDate.getTime()).toBeCloseTo(
162162
oneDayInMilliseconds,
163-
-2
163+
-2,
164164
);
165165
},
166166
});
@@ -188,3 +188,235 @@ test("verify sendEmail call", async () => {
188188
},
189189
});
190190
});
191+
192+
test("error when missing partner details for partner invite", async () => {
193+
await testApiHandler({
194+
appHandler,
195+
async test({ fetch }) {
196+
authMock.mockReturnValueOnce({
197+
user: { id: "1234", type: "SUPER_ADMIN" },
198+
expires: "",
199+
});
200+
201+
const formData = new FormData();
202+
formData.append("email", "test@test.com");
203+
formData.append("userType", "PARTNER");
204+
formData.append("name", "test name");
205+
const res = await fetch({ method: "POST", body: formData });
206+
expect(res.status).toBe(400);
207+
},
208+
});
209+
});
210+
211+
test("error when invalid partner details for partner invite", async () => {
212+
await testApiHandler({
213+
appHandler,
214+
async test({ fetch }) {
215+
authMock.mockReturnValueOnce({
216+
user: { id: "1234", type: "SUPER_ADMIN" },
217+
expires: "",
218+
});
219+
220+
const formData = new FormData();
221+
formData.append("email", "test@test.com");
222+
formData.append("userType", "PARTNER");
223+
formData.append("name", "test name");
224+
formData.append(
225+
"partnerDetails",
226+
JSON.stringify({
227+
siteName: 8,
228+
}),
229+
);
230+
const res = await fetch({ method: "POST", body: formData });
231+
expect(res.status).toBe(400);
232+
},
233+
});
234+
});
235+
236+
test("success when valid partner details for partner invite", async () => {
237+
await testApiHandler({
238+
appHandler,
239+
async test({ fetch }) {
240+
authMock.mockReturnValueOnce({
241+
user: { id: "1234", type: "SUPER_ADMIN" },
242+
expires: "",
243+
});
244+
245+
const testContact = {
246+
firstName: "test_firstName",
247+
lastName: "test_lastName",
248+
orgTitle: "test_orgTitle",
249+
primaryTelephone: "test_primaryTelephone",
250+
secondaryTelephone: "test_secondaryTelephone",
251+
};
252+
253+
const partnerDetails = {
254+
// General
255+
siteName: "test_siteName",
256+
address: "test_address",
257+
department: "test_department",
258+
gpsCoordinates: "test_gpsCoordinates",
259+
website: "test_website",
260+
socialMedia: "test_socialMedia",
261+
262+
// Contact
263+
regionalContact: testContact,
264+
medicalContact: testContact,
265+
adminDirectorContact: testContact,
266+
pharmacyContact: testContact,
267+
contactWhatsAppName: "test_contactWhatsAppName",
268+
contactWhatsAppNumber: "test_contactWhatsAppNumber",
269+
270+
// Introduction
271+
organizationHistory: "test_organizationHistory",
272+
supportRequested: "mobile_clinic_support",
273+
yearOrganizationEstablished: 2025,
274+
registeredWithMssp: true,
275+
proofOfRegistationWithMssp: "https://www.google.com/", // this is a URL to the file upload
276+
programUpdatesSinceLastReport: "test_programUpdatesSinceLastReport",
277+
278+
// Facility
279+
facilityType: [
280+
"birthing_center",
281+
"clinic",
282+
"hospital",
283+
"elderly_care",
284+
"rehabilitation_center",
285+
"dispensary",
286+
"orphanage",
287+
"primary_care",
288+
"health_center",
289+
"community_health_education",
290+
"nutrition_feeding",
291+
"secondary_tertiary_healthcare",
292+
],
293+
organizationType: ["non_profit", "for_profit", "faith_based"],
294+
governmentRun: true,
295+
emergencyMedicalRecordsSystemPresent: true,
296+
emergencyMedicalRecordsSystemName: "test",
297+
numberOfInpatientBeds: 10,
298+
numberOfPatientsServedAnnually: 10,
299+
communityMobileOutreachOffered: true,
300+
communityMobileOutreachDescription: "test",
301+
302+
// Infrastructure and Services
303+
facilityDescription: "test",
304+
cleanWaterAccessible: true,
305+
cleanWaterDescription: "test",
306+
closestSourceOfCleanWater: "test",
307+
sanitationFacilitiesPresent: true,
308+
sanitationFacilitiesLockableFromInside: true,
309+
electricityAvailable: true,
310+
accessibleByDisablePatients: true,
311+
medicationDisposalProcessDefined: true,
312+
medicationDisposalProcessDescription: "test",
313+
pickupVehiclePresent: true,
314+
pickupVehicleType: "test",
315+
pickupLocations: ["les_cayes", "port_au_prince"],
316+
317+
// Programs and Services Provided
318+
medicalServicesProvided: [
319+
"cancer",
320+
"dentistry",
321+
"dermatology",
322+
"hematology",
323+
"immunizations",
324+
"parasitic_infections",
325+
"acute_respiratory_infections",
326+
"vector_borne_diseases",
327+
"chronic_diseases",
328+
"diarrheal_diseases",
329+
"vaccine_preventable_diseases",
330+
"infectious_diseases",
331+
"neurology",
332+
"malnutrition",
333+
"ophthalmology",
334+
"ears_nose_throat",
335+
"orthopedics_and_rehabilitation",
336+
"pediatrics",
337+
"radiology",
338+
"wound_care",
339+
"maternal_care",
340+
"lab_tests",
341+
"trauma_and_surgery",
342+
"urology",
343+
],
344+
otherMedicalServicesProvided: "test",
345+
346+
// Finances
347+
patientsWhoCannotPay: "test",
348+
percentageOfPatientsNeedingFinancialAid: 10,
349+
percentageOfPatientsReceivingFreeTreatment: 10,
350+
annualSpendingOnMedicationsAndMedicalSupplies: "5001_to_10000",
351+
numberOfPrescriptionsPrescribedAnnuallyTracked: true,
352+
numberOfTreatmentsPrescribedAnnually: 10,
353+
anyMenServedLastYear: true,
354+
menServedLastYear: 10,
355+
anyWomenServedLastYear: true,
356+
womenServedLastYear: 10,
357+
anyBoysServedLastYear: true,
358+
boysServedLastYear: 10,
359+
anyGirlsServedLastYear: true,
360+
girlsServedLastYear: 10,
361+
anyBabyBoysServedLastYear: true,
362+
babyBoysServedLastYear: 10,
363+
anyBabyGirlsServedLastYear: true,
364+
babyGirlsServedLastYear: 10,
365+
totalPatientsServedLastYear: 10,
366+
367+
// Staff
368+
numberOfDoctors: 10,
369+
numberOfNurses: 10,
370+
numberOfMidwives: 10,
371+
numberOfAuxilaries: 10,
372+
numberOfStatisticians: 10,
373+
numberOfPharmacists: 10,
374+
numberOfCHW: 10,
375+
numberOfAdministrative: 10,
376+
numberOfHealthOfficers: 10,
377+
totalNumberOfStaff: 10,
378+
other: "test",
379+
380+
// Medical Supplies
381+
mostNeededMedicalSupplies: [
382+
"anesthetics",
383+
"antipyretics_nsaids",
384+
"antiallergics",
385+
"anti_infectives",
386+
"antineoplastics",
387+
"cardiovascular",
388+
"dermatological",
389+
"diagnostics",
390+
"diuretics",
391+
"gastrointestinal",
392+
"ophthalmological",
393+
"respiratory",
394+
"replacements",
395+
"vitamins_minerals",
396+
"bandages",
397+
"braces",
398+
"hospital_consumables",
399+
"dental",
400+
"diagnostic",
401+
"personal_care",
402+
"Prosthetics",
403+
"respiratory ",
404+
"surgical ",
405+
"syringes_needles",
406+
],
407+
otherSpecialityItemsNeeded: "test",
408+
};
409+
410+
const formData = new FormData();
411+
formData.append("email", "test@test.com");
412+
formData.append("userType", "PARTNER");
413+
formData.append("name", "test name");
414+
formData.append("partnerDetails", JSON.stringify(partnerDetails));
415+
const res = await fetch({ method: "POST", body: formData });
416+
expect(res.status).toBe(200);
417+
418+
const createdInvite = dbMock.userInvite.create.mock.calls[0][0].data;
419+
expect(createdInvite.partnerDetails).toEqual(partnerDetails);
420+
},
421+
});
422+
});

src/app/api/invites/route.ts

+17-6
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,22 @@ import {
1616
conflictError,
1717
ok,
1818
} from "@/util/responses";
19+
import { partnerDetailsSchema } from "@/schema/partnerDetails";
1920

20-
const schema = zfd.formData({
21-
email: zfd.text(z.string().email()),
22-
name: zfd.text(z.string()),
23-
userType: zfd.text(z.nativeEnum(UserType)),
24-
});
21+
const schema = zfd
22+
.formData({
23+
email: zfd.text(z.string().email()),
24+
name: zfd.text(z.string()),
25+
userType: zfd.text(z.nativeEnum(UserType)),
26+
partnerDetails: zfd.json(partnerDetailsSchema).optional(),
27+
})
28+
.refine(
29+
(data) => !(data.userType === UserType.PARTNER && !data.partnerDetails),
30+
{
31+
message: "Partner details are required for PARTNER user type",
32+
path: ["partnerDetails"],
33+
},
34+
);
2535

2636
/**
2737
* Create a new user invite (expires in 1 day) and sends email to user.
@@ -47,7 +57,7 @@ export async function POST(request: NextRequest) {
4757
if (!parseResult.success) {
4858
return argumentError("Invalid form data");
4959
}
50-
const { email, name, userType } = parseResult.data;
60+
const { email, name, userType, partnerDetails } = parseResult.data;
5161

5262
const existingUser = await db.user.findFirst({ where: { email } });
5363
if (existingUser) {
@@ -65,6 +75,7 @@ export async function POST(request: NextRequest) {
6575
token,
6676
expiration,
6777
userType,
78+
partnerDetails,
6879
},
6980
});
7081

0 commit comments

Comments
 (0)