Skip to content

Commit

Permalink
prompt routes
Browse files Browse the repository at this point in the history
  • Loading branch information
ap-justin committed Oct 18, 2024
1 parent 769a9ea commit c41e923
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 142 deletions.
12 changes: 9 additions & 3 deletions src/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { appRoutes, donateWidgetRoutes } from "constants/routes";
import ModalContext from "contexts/ModalContext";
import { RouterErrorBoundary } from "errors/ErrorBoundary";
import { ErrorElement } from "errors/ErrorElement";
import NProgress from "nprogress";
import { adminRoute } from "pages/Admin";
import { promptRoutes, reviewRoute } from "pages/Application/review-route";
import { routes as blogRoutes } from "pages/Blog";
import { legalRoutes } from "pages/Legal";
import OAuthRedirector from "pages/OAuthRedirector";
Expand Down Expand Up @@ -53,7 +54,12 @@ const _appRoutes: RO[] = [
{
path: appRoutes.applications,
children: [
{ path: ":id", lazy: () => import("pages/Application") },
{
path: ":id",
lazy: () => import("pages/Application"),
errorElement: <ErrorElement />,
children: [reviewRoute, ...promptRoutes],
},
{ index: true, lazy: () => import("pages/Applications") },
],
},
Expand Down Expand Up @@ -111,7 +117,7 @@ export const routes: RO[] = [
loader: rootLoader,
action: rootAction,
children: rootRoutes,
ErrorBoundary: RouterErrorBoundary,
ErrorBoundary: ErrorElement,
},
];

Expand Down
23 changes: 1 addition & 22 deletions src/components/Prompt/Prompt.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Modal from "components/Modal";
import { useModalContext } from "contexts/ModalContext";
import Icon from "../Icon";
import LoaderRing from "../LoaderRing";
import { PromptIcon } from "./prompt-icon";
import type { Props } from "./types";

export default function Prompt({
Expand Down Expand Up @@ -50,24 +50,3 @@ export default function Prompt({
</Modal>
);
}

function PromptIcon({
type,
classes = "",
}: Pick<Props, "type"> & { classes?: string }) {
const common = `justify-self-center ${classes}`;
switch (type) {
case "success":
return (
<Icon type="CheckCircle" size={92} className={common + " text-green"} />
);
case "error":
return (
<Icon type="Exclamation" size={80} className={common + " text-red"} />
);
case "loading":
return <LoaderRing thickness={12} classes={common + " h-24"} />;
default:
return null;
}
}
53 changes: 53 additions & 0 deletions src/components/Prompt/PromptV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Dialog, DialogBackdrop, DialogPanel } from "@headlessui/react";
import type { PropsWithChildren, ReactNode } from "react";
import { Link, useNavigate } from "react-router-dom";
import Icon from "../Icon";
import { PromptIcon } from "./prompt-icon";

interface Props extends PropsWithChildren {
type?: "success" | "error" | "loading";
ack?: ReactNode;
}

export default function PromptV2({ type, children }: Props) {
const navigate = useNavigate();
return (
<Dialog
open={true}
onClose={() =>
navigate("..", { preventScrollReset: true, replace: true })
}
className="relative z-50"
>
<DialogBackdrop className="fixed inset-0 bg-black/30 data-[closed]:opacity-0" />
<DialogPanel className="fixed-center z-10 grid text-navy-d4 bg-white sm:w-full w-[90vw] sm:max-w-lg rounded overflow-hidden">
<div className="flex justify-end p-4 border-b border-gray-l4">
<Link
to=".."
preventScrollReset
replace
className="border border-gray-l4 p-2 rounded-md"
>
<Icon type="Close" size={24} />
</Link>
</div>

<PromptIcon type={type} classes="mb-6 sm:mb-8 mt-4 sm:mt-12" />
<div className="px-6 pb-4 text-center text-navy-l1 dark:text-navy-l2">
{children}
</div>
<div className="p-3 sm:px-8 sm:py-4 empty:h-12 w-full text-center sm:text-right bg-blue-l5 border-t border-gray-l4">
<Link
to=".."
preventScrollReset
replace
type="button"
className="inline-block btn-blue px-8 py-2 max-sm:w-full"
>
{type === "success" ? "Done" : "Ok"}
</Link>
</div>
</DialogPanel>
</Dialog>
);
}
24 changes: 24 additions & 0 deletions src/components/Prompt/prompt-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import LoaderRing from "components/LoaderRing";
import Icon from "../Icon";
import type { Props } from "./types";

export function PromptIcon({
type,
classes = "",
}: Pick<Props, "type"> & { classes?: string }) {
const common = `justify-self-center ${classes}`;
switch (type) {
case "success":
return (
<Icon type="CheckCircle" size={92} className={common + " text-green"} />
);
case "error":
return (
<Icon type="Exclamation" size={80} className={common + " text-red"} />
);
case "loading":
return <LoaderRing thickness={12} classes={common + " h-24"} />;
default:
return null;
}
}
7 changes: 6 additions & 1 deletion src/errors/DefaultFallback.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import Icon from "components/Icon";
import { GENERIC_ERROR_MESSAGE } from "constants/common";
import type { ReactNode } from "react";

export default function DefaultFallback() {
interface Props {
acknowledger?: ReactNode;
}
export default function DefaultFallback({ acknowledger }: Props) {
return (
<div className="grid place-items-center content-center gap-6 p-4">
<Icon type="Exclamation" className="text-red text-[2em]" />
<p className="text-center">{GENERIC_ERROR_MESSAGE}</p>
{acknowledger}
</div>
);
}
7 changes: 0 additions & 7 deletions src/errors/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
type PropsWithChildren,
type ReactNode,
} from "react";
import { useRouteError } from "react-router-dom";
import DefaultFallback from "./DefaultFallback";

type Props = PropsWithChildren<{ fallback?: ReactNode }>;
Expand Down Expand Up @@ -34,9 +33,3 @@ export default class ErrorBoundary extends Component<Props, State> {
: this.props.children;
}
}

export function RouterErrorBoundary() {
const error = useRouteError();
logger.error(error);
return <DefaultFallback />;
}
34 changes: 34 additions & 0 deletions src/errors/ErrorElement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { logger } from "helpers";
import { useEffect, useRef } from "react";
import { Link, useRouteError } from "react-router-dom";
import DefaultFallback from "./DefaultFallback";

export function ErrorElement() {
const error = useRouteError();

const elementRef = useRef<HTMLAnchorElement>(null);

//biome-ignore lint: log onmount only
useEffect(() => {
logger.error(error);
}, []);

useEffect(() => {
if (!elementRef.current) return;
elementRef.current.scrollIntoView({ block: "center" });
}, []);

return (
<DefaultFallback
acknowledger={
<Link
ref={elementRef}
to="."
className="border border-gray-l4 rounded-lg px-6 py-2"
>
OK
</Link>
}
/>
);
}
34 changes: 13 additions & 21 deletions src/pages/Application/Loaded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,11 @@ 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 { Link, Outlet } from "react-router-dom";
import Container from "./Container";
import Prompt from "./Prompt";

export default function Loaded(props: Application) {
const { showModal } = useModalContext();

const review = (verdict: "approve" | "reject") => () => {
showModal(Prompt, {
verdict,
uuid: props.id,
orgName: props.contact.org_name,
});
};

const prevVerdict =
props.status === "03"
? "approved"
Expand Down Expand Up @@ -121,22 +109,26 @@ export default function Loaded(props: Application) {
>
back
</Link>
<button
disabled={!!prevVerdict}
onClick={review("reject")}
<Link
aria-disabled={!!prevVerdict}
to={`rejected?org_name=${props.contact.org_name}`}
type="button"
className="px-4 py-1 min-w-[6rem] text-sm uppercase btn-red"
preventScrollReset
>
reject
</button>
<button
disabled={!!prevVerdict}
onClick={review("approve")}
</Link>
<Link
aria-disabled={!!prevVerdict}
to={`approved?org_name=${props.contact.org_name}`}
type="button"
className="px-4 py-1 min-w-[6rem] text-sm uppercase btn-green"
preventScrollReset
>
approve
</button>
</Link>
{/** review route renders here */}
<Outlet />
</div>
</>
);
Expand Down
Loading

0 comments on commit c41e923

Please sign in to comment.