diff --git a/package.json b/package.json index cddf3d1479..4014270ea7 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@better-giving/assets": "1.0.18", - "@better-giving/registration": "1.0.1", + "@better-giving/registration": "1.0.8", "@gsap/react": "2.1.1", "@headlessui/react": "2.1.0", "@hookform/error-message": "2.0.1", diff --git a/src/pages/Application/Loaded.tsx b/src/pages/Application/Loaded.tsx index 8877c936d5..494bc9f288 100644 --- a/src/pages/Application/Loaded.tsx +++ b/src/pages/Application/Loaded.tsx @@ -1,34 +1,39 @@ +import type { Application } from "@better-giving/registration/approval"; +import { isIrs501c3, isRejected } from "@better-giving/registration/models"; import ExtLink from "components/ExtLink"; import Icon from "components/Icon"; import { appRoutes } from "constants/routes"; import { useModalContext } from "contexts/ModalContext"; import type { PropsWithChildren } from "react"; import { Link } from "react-router-dom"; -import type { ApplicationDetails } from "types/aws"; import Container from "./Container"; import Prompt from "./Prompt"; const NA = "Not provided"; -export default function Loaded(props: ApplicationDetails) { +export default function Loaded(props: Application) { const { showModal } = useModalContext(); - const { ContactPerson: c, Registration: r, WiseRecipient: w } = props; - const doc = r.Documentation; const review = (verdict: "approve" | "reject") => () => { - showModal(Prompt, { verdict, uuid: c.PK, orgName: r.OrganizationName }); + showModal(Prompt, { + verdict, + uuid: props.id, + orgName: props.contact.org_name, + }); }; const prevVerdict = - r.RegistrationStatus === "Active" || r.RegistrationStatus === "Rejected" - ? r.RegistrationStatus - : null; + props.status === "02" + ? "approved" + : props.status === "03" + ? "rejected" + : null; - const claim = doc.DocType === "Non-FSA" ? doc.Claim : undefined; + const claim = isIrs501c3(props.docs) ? props.docs.claim : null; return ( <> -

{r.OrganizationName}

+

{props.contact.org_name}

{claim && ( - {prevVerdict === "Active" ? "Approved" : "Rejected"} + {prevVerdict === "approved" ? "Approved" : "Rejected"} )} - {r.RegistrationStatus === "Rejected" && ( + {isRejected(props.submission) && (
Rejection reason: - {r.RejectionReason} + + {props.submission.rejection} +
)}
Application ID: - {r.PK} + {props.id}
Date submitted: - {new Date(r.RegistrationDate).toLocaleDateString()} + {new Date(props.created_at).toLocaleDateString()}
- {doc.DocType === "FSA" ? ( - {doc.RegistrationNumber} + {!isIrs501c3(props.docs) ? ( + {props.docs.registration_number} ) : ( - {doc.EIN} + {props.docs.ein} )} - {r.HqCountry} + {props.org.hq_country} - {r.ActiveInCountries.join(", ")} + {props.org.active_in_countries?.join(", ") ?? "N/A"} + + {props.org.un_sdg.join(", ")} + + {props.contact.first_name + " " + props.contact.last_name} - {r.UN_SDG.join(", ")} - {c.FirstName + " " + c.LastName} - {c.Email} - {doc.DocType === "FSA" && ( + {props.registrant_id} + {!isIrs501c3(props.docs) && ( <> - + - + - + )} @@ -96,12 +105,12 @@ export default function Loaded(props: ApplicationDetails) {
- {w?.bankName || NA} - {w?.address || NA} - {w?.accountNumber || NA} - {w?.accountName || NA} + {props.bank?.bankName || NA} + {props.bank?.address || NA} + {props.bank?.accountNumber || NA} + {props.bank?.accountName || NA} - +
diff --git a/src/pages/Application/Prompt.tsx b/src/pages/Application/Prompt.tsx index 90e20c735a..baedba152e 100644 --- a/src/pages/Application/Prompt.tsx +++ b/src/pages/Application/Prompt.tsx @@ -41,10 +41,10 @@ export default function Prompt({ verdict, orgName, uuid }: Props) { const onSubmit: SubmitHandler = async (fv) => { setModalOption("isDismissible", false); const result = await review({ - PK: uuid, + id: uuid, ...(verdict === "approve" ? { verdict: "approved" } - : { verdict: "rejected", rejectionReason: fv.reason ?? "" }), + : { verdict: "rejected", reason: fv.reason ?? "" }), }); if ("error" in result) { diff --git a/src/pages/Applications/Applications.tsx b/src/pages/Applications/Applications.tsx index c29fc0c9a8..941112b159 100644 --- a/src/pages/Applications/Applications.tsx +++ b/src/pages/Applications/Applications.tsx @@ -48,7 +48,7 @@ function Applications() { /> [] = [ - { label: "Rejected", value: "Rejected" }, - { label: "Under Review", value: "Under Review" }, - { label: "Approved", value: "Active" }, +export const statuses: OptionType>[] = [ + { label: "Rejected", value: "04" }, + { label: "Under Review", value: "02" }, + { label: "Approved", value: "03" }, ]; diff --git a/src/pages/Applications/Filter/types.ts b/src/pages/Applications/Filter/types.ts index 8b7d70143a..cb6663432b 100644 --- a/src/pages/Applications/Filter/types.ts +++ b/src/pages/Applications/Filter/types.ts @@ -1,10 +1,10 @@ -import type { RegistrationStatus } from "types/aws"; +import type { Status } from "@better-giving/registration/models"; import type { Country, OptionType } from "types/components"; export type FormValues = { startDate: string; endDate: string; - status: OptionType; + status: OptionType>; hqCountry: Country; //meta donorAddress: string; diff --git a/src/pages/Applications/Table.tsx b/src/pages/Applications/Table.tsx index b8a2f0e7b9..55a7d2e74c 100644 --- a/src/pages/Applications/Table.tsx +++ b/src/pages/Applications/Table.tsx @@ -1,10 +1,13 @@ +import { + type Status as TStatus, + isIrs501c3, +} from "@better-giving/registration/models"; import { HeaderButton } from "components/HeaderButton"; import Icon from "components/Icon"; import TableSection, { Cells } from "components/TableSection"; import { appRoutes } from "constants/routes"; import useSort from "hooks/useSort"; import { Link } from "react-router-dom"; -import type { RegistrationStatus } from "types/aws"; import LoadMoreBtn from "./LoadMoreBtn"; import type { TableProps } from "./types"; @@ -18,7 +21,7 @@ export default function Table({ }: TableProps) { const { handleHeaderClick, sorted, sortDirection, sortKey } = useSort( applications, - "RegistrationDate" + "created_at" ); return ( @@ -35,7 +38,7 @@ export default function Table({ > type ( - {row.Documentation.DocType === "Non-FSA" && - row.Documentation.Claim - ? "Claim" - : "New"} + {isIrs501c3(row.docs) && row.docs.claim ? "Claim" : "New"} - <>{row.OrganizationName} - <>{new Date(row.RegistrationDate).toLocaleDateString()} - <>{row.HqCountry} + <>{row.org_name} + <>{new Date(row.created_at).toLocaleDateString()} + <>{row.hq_country} - + ({ - limit: 10, - regStatus: "Under Review", + const [params, setParams] = useState({ + status: "02", }); const queryState = useApplicationsQuery(params, { selectFromResult({ data, ...rest }) { - if (!data?.Items) { + if (!data?.items) { return { data, ...rest }; } - const filtered = data?.Items.filter(({ OrganizationName, PK }) => - (OrganizationName + PK) - .toLowerCase() - .includes(debouncedQuery.toLowerCase()) + const filtered = data?.items.filter(({ org_name, id }) => + (org_name + id).toLowerCase().includes(debouncedQuery.toLowerCase()) ); return { - data: { Items: filtered, ItemCutoff: data.ItemCutoff }, + data: { items: filtered, nextPageKey: data.nextPageKey }, ...rest, }; }, @@ -45,27 +42,27 @@ export default function usePaginatedApplications() { async function loadNextPage() { //button is hidden when there's no more if ( - data?.ItemCutoff && + data?.nextPageKey && originalArgs /** cards won't even show if no initial query is made */ ) { const { data: newEndowRes } = await loadMore({ ...originalArgs, - start: data.ItemCutoff + 1, + nextPageKey: data.nextPageKey, }); if (newEndowRes) { //pessimistic update to original cache data dispatch( updateAWSQueryData("applications", originalArgs, (prevResult) => { - prevResult.Items.push(...newEndowRes.Items); - prevResult.ItemCutoff = newEndowRes.ItemCutoff; + prevResult.items.push(...newEndowRes.items); + prevResult.nextPageKey = newEndowRes.nextPageKey; }) ); } } } - const hasMore = !!data?.ItemCutoff; + const hasMore = !!data?.nextPageKey; return { data, diff --git a/src/services/aws/aws.ts b/src/services/aws/aws.ts index 3287ed6fc7..e2de02ba85 100644 --- a/src/services/aws/aws.ts +++ b/src/services/aws/aws.ts @@ -1,3 +1,9 @@ +import type { + Application, + Page, + QueryParams, + Verdict, +} from "@better-giving/registration/approval"; import { createApi, fetchBaseQuery, retry } from "@reduxjs/toolkit/query/react"; import { fetchAuthSession } from "aws-amplify/auth"; import { TEMP_JWT } from "constants/auth"; @@ -6,10 +12,6 @@ import { apiEnv } from "services/constants"; import type { RootState } from "store/store"; import { userIsSignedIn } from "types/auth"; import type { - Application, - ApplicationDetails, - ApplicationVerdict, - ApplicationsQueryParams, Donation, DonationsQueryParams, EndowListPaginatedAWSQueryRes, @@ -17,7 +19,6 @@ import type { EndowmentCard, EndowmentOption, EndowmentsQueryParams, - PaginatedAWSQueryRes, } from "types/aws"; import { version as v } from "../helpers"; import type { EndowmentUpdate, IdOrSlug } from "../types"; @@ -187,10 +188,7 @@ export const aws = createApi({ }, }), - applications: builder.query< - PaginatedAWSQueryRes, - ApplicationsQueryParams - >({ + applications: builder.query({ providesTags: ["applications"], query: (params) => { return { @@ -200,17 +198,16 @@ export const aws = createApi({ }; }, }), - application: builder.query({ + application: builder.query({ providesTags: ["application"], query: (uuid) => ({ - url: `${v(1)}/applications`, - params: { uuid }, + url: `${v(1)}/applications/${uuid}`, headers: { authorization: TEMP_JWT }, }), }), - reviewApplication: builder.mutation({ + reviewApplication: builder.mutation({ invalidatesTags: ["application", "applications"], - query: (verdict) => { + query: ({ id, ...verdict }) => { return { url: `${v(3)}/applications`, method: "PUT", diff --git a/src/services/types.ts b/src/services/types.ts index 993ac103c1..eb519654df 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1,9 +1,7 @@ -import type { Except } from "type-fest"; import type { BankingApplication, EndowmentProfileUpdate, EndowmentSettingsUpdate, - RegV2, V2RecipientAccount, } from "types/aws"; import type { SemiPartial } from "types/utils"; @@ -15,22 +13,6 @@ export type EndowmentUpdate = SemiPartial< export type ProgramDeleteMsg = { id: number; program_id: string }; -export type FSASigner = - | { - id: string; - first_name: string; - last_name: string; - role: string; - email: string; - docs: Except< - RegV2.FsaDocs, - "fsa_signed_doc_url" | "fsa_signing_url" | "outdated" - > & - Pick & - Pick; - } - | string; //signerEID; - export type BankingApplicationDetails = BankingApplication & V2RecipientAccount; export type IdOrSlug = diff --git a/src/types/aws/ap/applications.ts b/src/types/aws/ap/applications.ts index f07490f094..6e909d9268 100644 --- a/src/types/aws/ap/applications.ts +++ b/src/types/aws/ap/applications.ts @@ -1,14 +1,4 @@ import type { FileObject } from "../common"; -import type { RegistrationStatus } from "./registration"; - -export type ApplicationsQueryParams = { - limit?: number; // Number of items to be returned per request - start?: number; //to load next page, set start to ItemCutOff + 1 - hqCountry?: string; - regStatus?: RegistrationStatus; - regDateStart?: string; //ISO string - regDateEnd?: string; //ISO string -}; export type BankingApplicationsQueryParams = { status?: BankingApplicationStatus; diff --git a/src/types/aws/ap/registration.ts b/src/types/aws/ap/registration.ts deleted file mode 100644 index fc61366c1e..0000000000 --- a/src/types/aws/ap/registration.ts +++ /dev/null @@ -1,221 +0,0 @@ -import type { Except } from "type-fest"; -import type { UNSDG_NUMS } from "types/lists"; -import type { EndowDesignation } from "."; -import type { FileObject } from "../common"; - -export type RegistrationStatus = - | "Inactive" - | "Under Review" - | "Active" - | "Rejected"; - -type BankVerificationStatus = "Not Submitted" | "Under Review"; - -export type ReferralMethods = - | "" - | "referral" - | "better-giving-alliance" - | "discord" - | "facebook" - | "linkedin" - | "medium" - | "press" - | "search-engines" - | "twitter" - | "other"; - -export type ContactRoles = - | "" - | "board-member" - | "ceo" - | "cfo" - | "communications" - | "fundraising-finance" - | "leadership-team" - | "legal" - | "other" - | "president" - | "secretary" - | "treasurer" - | "vice-president"; - -export type EndowClaim = { - id: number; - name: string; - ein: string; -}; - -type InitReg = { - PK: string; - SK: "Registration"; - RegistrationDate: string /** ISO string*/; - RegistrationStatus: RegistrationStatus; - RejectionReason: string; - UN_SDG: UNSDG_NUMS[]; - bank_verification_status: BankVerificationStatus; - InitClaim?: EndowClaim; -}; - -export type InitContact = { - PK: string; - SK: "ContactPerson"; - Email: string; -}; - -//INIT STEP -export type InitApplication = { - Registration: InitReg; - ContactPerson: InitContact; -}; - -type Append = { - Registration: Reg["Registration"] & R; - ContactPerson: Reg["ContactPerson"] & C; -}; - -export type OrgDataForStep1 = { OrganizationName: string }; - -export type ContactDetails = { - PK: string; - FirstName: string; - LastName: string; - PhoneNumber: string; - Goals: string; - Role: ContactRoles; - OtherRole: string; - ReferralMethod: ReferralMethods | "angel-alliance"; //legacy - ReferralCode: string; //when ReferralMethod is "referral" - OtherReferralMethod: string; //when ReferralMethod is "other" -}; - -export type OrgDetails = { - Website: string; - HqCountry: string; - ActiveInCountries: string[]; - EndowDesignation: EndowDesignation | ""; - KycDonorsOnly: boolean; - UN_SDG: UNSDG_NUMS[]; -}; - -export type FSAInquiry = { - AuthorizedToReceiveTaxDeductibleDonations: boolean; -}; - -export type FSASignerDocumentation = { - OrgName: string; - HqCountry: string; - RegistrationNumber: string; - ProofOfRegistration: FileObject; - ProofOfIdentity: FileObject; - LegalEntityType: string; - ProjectDescription: string; -}; - -export type NonFSADocumentation = { - DocType: "Non-FSA"; - EIN: string; - Claim?: EndowClaim | null; -}; - -export type FSADocumentation = Except< - FSASignerDocumentation, - "HqCountry" | "OrgName" -> & { - DocType: "FSA"; - FiscalSponsorshipAgreementSigningURL?: string; - SignedFiscalSponsorshipAgreement?: string; -}; - -export type TDocumentation = { - Documentation: FSADocumentation | NonFSADocumentation; -}; - -export type BankingDetails = { - BankStatementFile: FileObject; - wise_recipient_id: number; -}; - -type DoneContact = Append; -export type DoneOrgDetails = Append; - -export type DoneFSAInquiry = Append; - -export type DoneDocs = Append; - -export type DoneBanking = Append; - -type SubmissionDetails = { Email: string; EndowmentId?: number }; - -type InReview = Append; - -export type Application = InReview["Registration"] & Pick; - -type WiseRecipient = { - accountName: string; - accountNumber: string; - address: string; - bankName: string; -}; - -export type ApplicationDetails = InReview & { WiseRecipient?: WiseRecipient }; - -//could be futher simplified to just {verdict: "approved" | string} -export type ApplicationVerdict = { PK: string } & ( - | { verdict: "approved" } - | { verdict: "rejected"; rejectionReason: string } -); - -export namespace RegV2 { - namespace Update { - export interface Contact extends RegV2.Contact { - type: "contact"; - } - export interface Org extends Omit { - type: "org"; - } - export interface FsaInq extends RegV2.FsaInq { - type: "fsa-inq"; - } - export interface Docs - extends Omit { - type: "docs"; - } - export interface Banking extends RegV2.Banking { - type: "banking"; - } - } - - export type Update = ( - | Update.Contact - | Update.Org - | Update.FsaInq - | Update.Docs - | Update.Banking - ) & { id: string }; -} - -export const isIrs501c3 = (docs: RegV2.Docs): docs is RegV2.TaxDeductibleDocs => - "ein" in docs; - -// type guards -class StepChecker { - contact = (data: RegV2.Record): data is RegV2.Step1 => "contact" in data; - org = (data: RegV2.Record): data is RegV2.Step2 => - "org" in data && this.contact(data); - fsaInq = (data: RegV2.Record): data is RegV2.Step3 => - "irs501c3" in data && this.org(data); - docs = (data: RegV2.Record): data is RegV2.Step4 => - "docs" in data && this.fsaInq(data) && !data.docs.outdated; - banking = (data: RegV2.Record): data is RegV2.Step5 => - "banking" in data && - this.docs(data) && - ("ein" in data.docs - ? true - : //check if FSA docs are complete - !!data.docs.fsa_signed_doc_url && !!data.docs.fsa_signing_url); - - submission = (data: RegV2.Record): data is RegV2.Step6 => - "submission" in data && this.banking(data); -} - -export const isDone = new StepChecker(); diff --git a/src/types/aws/index.ts b/src/types/aws/index.ts index 64f91aedbb..c64683e602 100644 --- a/src/types/aws/index.ts +++ b/src/types/aws/index.ts @@ -1,6 +1,5 @@ export * from "./common"; export * from "./ap"; -export * from "./ap/registration"; export * from "./ap/applications"; export * from "./ap/bankDetails"; export * from "./ap/donations"; diff --git a/yarn.lock b/yarn.lock index 5b8604c317..2eb3f9494e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -822,12 +822,12 @@ __metadata: languageName: node linkType: hard -"@better-giving/registration@npm:1.0.1": - version: 1.0.1 - resolution: "@better-giving/registration@npm:1.0.1" +"@better-giving/registration@npm:1.0.8": + version: 1.0.8 + resolution: "@better-giving/registration@npm:1.0.8" peerDependencies: valibot: 0.42.0 - checksum: 10/7ecf268dc627e9853d9ec6afd6ac7f000170fb270545c3225999d32494ed22be34efb28bf96f4ac339584ddcf32fa9ff3deb68f756f7671dd479744f548d58f1 + checksum: 10/bde23e708805ec33c9b8924fcc068a2d9ebfad2e38f255cc33ce9df1cbc4fcae05c661cb3d452502c89f6718a2d44504c910be0c1a5adc145eff9ea06c9051eb languageName: node linkType: hard @@ -3080,7 +3080,7 @@ __metadata: resolution: "angelprotocol-web-app@workspace:." dependencies: "@better-giving/assets": "npm:1.0.18" - "@better-giving/registration": "npm:1.0.1" + "@better-giving/registration": "npm:1.0.8" "@biomejs/biome": "npm:1.8.1" "@gsap/react": "npm:2.1.1" "@headlessui/react": "npm:2.1.0"