Skip to content

Commit

Permalink
approval prompts
Browse files Browse the repository at this point in the history
  • Loading branch information
ap-justin committed Oct 6, 2024
1 parent 2d381cf commit f8f7b7d
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 70 deletions.
2 changes: 2 additions & 0 deletions src/components/Icon/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
Search,
Settings,
Shield,
Split,
Sprout,
SquareArrowOutUpRight,
Star,
Expand Down Expand Up @@ -130,6 +131,7 @@ export const icons = {
Save: Save,
Search,
SecurityScan: Shield,
Split,
Sprout,
Star,
StickyNote,
Expand Down
140 changes: 103 additions & 37 deletions src/pages/Admin/Charity/Funds/FundItem.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,124 @@
import type { FundItem as TFundItem } from "@better-giving/fundraiser";
import Icon from "components/Icon";
import Prompt from "components/Prompt";
import { appRoutes } from "constants/routes";
import { useAuthenticatedUser } from "contexts/Auth";
import { useErrorContext } from "contexts/ErrorContext";
import { useModalContext } from "contexts/ModalContext";
import { Link } from "react-router-dom";
import { useOptOutMutation } from "services/aws/endow-funds";
import {
useApproveMutation,
useOptOutMutation,
} from "services/aws/endow-funds";

export const FundItem = (props: TFundItem & { endowId: number }) => {
const user = useAuthenticatedUser();
const isActive = new Date().toISOString() <= props.expiration && props.active;
const isEditor = user.funds.includes(props.id);
const [optOut, { isLoading: isOptingOut }] = useOptOutMutation();
const [approve, { isLoading: isApproving }] = useApproveMutation();
const { showModal } = useModalContext();
const { handleError } = useErrorContext();

const isApproved = props.approvers.includes(props.endowId);

return (
<div className="grid grid-cols-subgrid col-span-5 items-center gap-3 rounded border border-gray-l4">
<img src={props.logo} className="object-cover h-full w-10" />
<span className="mr-4 p-1.5 font-medium text-navy-l1">{props.name}</span>
<p
className={`uppercase justify-self-start text-2xs rounded-full px-3 py-0.5 ${
isActive ? "text-green bg-green-l4" : "text-red bg-red-l4"
}`}
>
{isActive ? "active" : "closed"}
</p>
<Link
aria-disabled={!isActive || !isEditor}
className={`text-sm hover:text-blue-d1 text-blue uppercase p-3 aria-disabled:pointer-events-none aria-disabled:text-gray ${
isEditor ? "" : "invisible"
}`}
to={`${appRoutes.funds}/${props.id}/edit`}
>
edit
</Link>
<div className="grid grid-cols-subgrid col-span-6 items-center rounded border border-gray-l4">
<img
src={props.logo}
className="col-start-1 row-span-2 object-cover w-28 aspect-square border-r border-gray-l4"
/>
<Link
className="text-sm hover:text-blue-d1 text-blue uppercase p-3"
to={`${appRoutes.funds}/${props.id}`}
className="mr-4 pl-4 p-1.5 font-medium text-navy-l1 hover:text-blue-d1"
>
view
<span className="font-heading">{props.name}</span>

<span
className={`ml-1 relative bottom-px uppercase text-center text-2xs rounded-full px-3 py-0.5 ${
isActive ? "text-green bg-green-l4" : "text-red bg-red-l4"
}`}
>
{isActive ? "active" : "closed"}
</span>
</Link>
{/** show only when this endow is member of fund */}
<button
disabled={isOptingOut}
type="button"
onClick={async () => {
console.log(props.id);
try {
await optOut({ fundId: props.id, endowId: props.endowId }).unwrap();
} catch (err) {
handleError(err, { context: "opting out of fund" });
}
}}
>
{isOptingOut ? "opting out.." : "opt-out"}
</button>

<div className="grid grid-cols-subgrid col-start-2 col-span-5 border-t border-gray-l4 gap-x-6 p-3 items-center">
<Link
aria-disabled={!isActive || !isEditor}
className={`flex items-center gap-x-1 text-sm hover:text-blue-d1 text-blue aria-disabled:pointer-events-none aria-disabled:text-gray ${
isEditor ? "" : "invisible"
}`}
to={`${appRoutes.funds}/${props.id}/edit`}
>
<Icon type="ArrowRight" size={16} />
<span>Edit</span>
</Link>
{/** fund item won't show once NPO opted out of it: so no need to hide this button */}
<button
className="font-heading bg-amber enabled:hover:bg-amber-d1 text-white rounded-full px-4 py-2 text-xs flex items-center gap-1 disabled:bg-gray-l1"
disabled={isOptingOut}
type="button"
onClick={async () => {
console.log(props.id);
try {
await optOut({
fundId: props.id,
endowId: props.endowId,
}).unwrap();
showModal(Prompt, {
type: "success",
children:
"You have successfully opted out of this fund. Changes will take effect shortly.",
});
} catch (err) {
handleError(err, { context: "opting out of fund" });
}
}}
>
<Icon
type={isOptingOut ? "Loading" : "Split"}
size={12}
className={isOptingOut ? "animate-spin" : "rotate-90"}
/>
<span>{isOptingOut ? "Opting out.." : "Opt out"}</span>
</button>
{!isApproved ? (
<button
className="font-heading bg-blue-d1 text-white rounded-full px-4 py-2 text-xs flex items-center gap-1 disabled:bg-gray-l1"
disabled={isApproving}
type="button"
onClick={async () => {
console.log(props.id);
try {
await approve({
fundId: props.id,
endowId: props.endowId,
}).unwrap();
showModal(Prompt, {
type: "success",
children:
"You have successfully approved this fund. Changes will take effect shortly.",
});
} catch (err) {
handleError(err, { context: "approving fund" });
}
}}
>
<Icon
type={isApproving ? "Loading" : "Verified"}
size={16}
className={isApproving ? "animate-spin" : ""}
/>
<span>{isApproving ? "Approving.." : "Approve"}</span>
</button>
) : (
<div className="flex items-center gap-1 text-green">
<Icon type="Verified" className="size-4" />
<p className="text-xs">Approved</p>
</div>
)}
</div>
</div>
);
};
2 changes: 1 addition & 1 deletion src/pages/Admin/Charity/Funds/Funds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function Funds() {
const { id } = useAdminContext();
const query = useFundsEndowMemberOfQuery({ endowId: id });
return (
<div className="grid gap-y-4 grid-cols-[auto_auto_1fr_auto_auto] justify-items-start">
<div className="grid gap-y-4 grid-cols-[auto_1fr_auto_auto_auto_auto] justify-items-start">
<h3 className="text-3xl mb-2 col-span-full">My Fundraisers</h3>

<QueryLoader
Expand Down
67 changes: 35 additions & 32 deletions src/services/aws/endow-funds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,39 @@ interface PathParams {
endowId: number;
fundId: string;
}
export const { useFundsEndowMemberOfQuery, useOptOutMutation } =
aws.injectEndpoints({
endpoints: (builder) => ({
fundsEndowMemberOf: builder.query<FundItem[], { endowId: number }>({
providesTags: ["endow-funds"],
query: ({ endowId }) => {
return {
url: `${v(8)}/endowments/${endowId}/funds`,
};
},
}),
optOut: builder.mutation<unknown, PathParams>({
invalidatesTags: ["endow-funds"],
query: ({ fundId, endowId }) => {
return {
url: `${v(8)}/endowments/${endowId}/funds/${fundId}/opt-out`,
method: "POST",
headers: { authorization: TEMP_JWT },
};
},
}),
approve: builder.mutation<unknown, PathParams>({
invalidatesTags: ["endow-funds"],
query: ({ fundId, endowId }) => {
return {
url: `${v(8)}/endowments/${endowId}/funds/${fundId}/approve`,
method: "POST",
headers: { authorization: TEMP_JWT },
};
},
}),
export const {
useFundsEndowMemberOfQuery,
useOptOutMutation,
useApproveMutation,
} = aws.injectEndpoints({
endpoints: (builder) => ({
fundsEndowMemberOf: builder.query<FundItem[], { endowId: number }>({
providesTags: ["endow-funds"],
query: ({ endowId }) => {
return {
url: `${v(8)}/endowments/${endowId}/funds`,
};
},
}),
});
optOut: builder.mutation<unknown, PathParams>({
invalidatesTags: ["endow-funds"],
query: ({ fundId, endowId }) => {
return {
url: `${v(8)}/endowments/${endowId}/funds/${fundId}/opt-out`,
method: "POST",
headers: { authorization: TEMP_JWT },
};
},
}),
approve: builder.mutation<unknown, PathParams>({
invalidatesTags: ["endow-funds"],
query: ({ fundId, endowId }) => {
return {
url: `${v(8)}/endowments/${endowId}/funds/${fundId}/approve`,
method: "POST",
headers: { authorization: TEMP_JWT },
};
},
}),
}),
});

0 comments on commit f8f7b7d

Please sign in to comment.