-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: impl createBrowserRouter, add routes dict
- Loading branch information
1 parent
f40ea79
commit 8cfc1bd
Showing
6 changed files
with
622 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { createBrowserRouter, redirect, RouterProvider } from "react-router-dom"; | ||
import { toast } from "react-toastify"; | ||
import * as Sentry from "@sentry/react"; | ||
import { ErrorBoundary } from "@/components/ErrorBoundary"; | ||
import { RootAppLayout } from "@/layouts/RootAppLayout"; | ||
import { checkoutValuesStore } from "@/stores/checkoutValuesStore"; | ||
import { APP_PATHS, APP_PATH_COMPONENTS } from "./appPaths"; | ||
import { getProtectedRouteLoader } from "./getProtectedRouteLoader"; | ||
|
||
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouter(createBrowserRouter); | ||
|
||
const rootAppBrowserRouter = sentryCreateBrowserRouter( | ||
[ | ||
{ | ||
path: APP_PATHS.ROOT, | ||
element: <RootAppLayout />, | ||
errorElement: <ErrorBoundary />, | ||
children: [ | ||
{ | ||
index: true, | ||
lazy: () => import(/* webpackChunkName: "LandingPage" */ "@/pages/LandingPage"), | ||
}, | ||
{ | ||
path: APP_PATHS.REGISTER, | ||
lazy: () => import(/* webpackChunkName: "RegisterPage" */ "@/pages/RegisterPage"), | ||
}, | ||
{ | ||
path: APP_PATHS.LOGIN, | ||
lazy: () => import(/* webpackChunkName: "LoginPage" */ "@/pages/LoginPage"), | ||
}, | ||
{ | ||
path: APP_PATHS.ToS, | ||
lazy: () => | ||
import(/* webpackChunkName: "TermsOfServicePage" */ "@/pages/TermsOfServicePage"), | ||
}, | ||
{ | ||
path: APP_PATHS.PRIVACY, | ||
lazy: () => | ||
import(/* webpackChunkName: "PrivacyPolicyPage" */ "@/pages/PrivacyPolicyPage"), | ||
}, | ||
{ | ||
path: APP_PATHS.PRODUCTS, | ||
lazy: () => import(/* webpackChunkName: "ProductsPage" */ "@/pages/ProductsPage"), | ||
}, | ||
{ | ||
path: APP_PATHS.CHECKOUT, | ||
lazy: () => import(/* webpackChunkName: "CheckoutPage" */ "@/pages/CheckoutPage"), | ||
loader: getProtectedRouteLoader( | ||
{ authenticationRequired: true, paymentRequired: false }, | ||
() => { | ||
// Custom route requirement: user must have selected a subscription | ||
if (!checkoutValuesStore.get().selectedSubscription) { | ||
toast.info("Please select a subscription.", { toastId: "select-a-sub" }); | ||
throw redirect(APP_PATHS.PRODUCTS); | ||
} | ||
} | ||
), | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.HOME, | ||
lazy: () => import(/* webpackChunkName: "HomePageLayout" */ "@/layouts/HomePageLayout"), | ||
loader: getProtectedRouteLoader({ authenticationRequired: true, paymentRequired: true }), | ||
children: [ | ||
{ | ||
lazy: () => | ||
import( | ||
/* webpackChunkName: "StripeConnectOnboardingStateLayer" */ "./StripeConnectOnboardingStateLayer" | ||
), | ||
children: [ | ||
{ | ||
index: true, | ||
lazy: () => import(/* webpackChunkName: "Dashboard" */ "@/pages/Dashboard"), | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.WORK_ORDERS, | ||
children: [ | ||
{ | ||
index: true, | ||
lazy: () => | ||
import( | ||
/* webpackChunkName: "WorkOrdersListView" */ "@/pages/WorkOrdersListView" | ||
), | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.FORM_VIEW, | ||
lazy: () => | ||
import( | ||
/* webpackChunkName: "WorkOrderFormView" */ "@/pages/WorkOrderFormView" | ||
), | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.ITEM_VIEW, | ||
lazy: () => | ||
import( | ||
/* webpackChunkName: "WorkOrderItemView" */ "@/pages/WorkOrderItemView" | ||
), | ||
}, | ||
], | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.INVOICES, | ||
children: [ | ||
{ | ||
index: true, | ||
lazy: () => | ||
import( | ||
/* webpackChunkName: "InvoicesListView" */ "@/pages/InvoicesListView" | ||
), | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.FORM_VIEW, | ||
lazy: () => | ||
import(/* webpackChunkName: "InvoiceFormView" */ "@/pages/InvoiceFormView"), | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.ITEM_VIEW, | ||
lazy: () => | ||
import(/* webpackChunkName: "InvoiceItemView" */ "@/pages/InvoiceItemView"), | ||
}, | ||
], | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.CONTACTS, | ||
children: [ | ||
{ | ||
index: true, | ||
lazy: () => | ||
import( | ||
/* webpackChunkName: "ContactsListView" */ "@/pages/ContactsListView" | ||
), | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.ITEM_VIEW, | ||
lazy: () => | ||
import(/* webpackChunkName: "ContactItemView" */ "@/pages/ContactItemView"), | ||
}, | ||
], | ||
}, | ||
{ | ||
path: APP_PATH_COMPONENTS.PROFILE, | ||
lazy: () => import(/* webpackChunkName: "ProfilePage" */ "@/pages/ProfilePage"), | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
path: "*", | ||
lazy: () => import(/* webpackChunkName: "PageNotFound" */ "@/pages/PageNotFound"), | ||
}, | ||
], | ||
}, | ||
], | ||
{ | ||
basename: APP_PATHS.ROOT, | ||
} | ||
); | ||
|
||
export const RootAppRouter = () => <RouterProvider router={rootAppBrowserRouter} />; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { useState, useEffect } from "react"; | ||
import { useSearchParams, Outlet } from "react-router-dom"; | ||
import Box from "@mui/material/Box"; | ||
import Text from "@mui/material/Typography"; | ||
import AnnouncementIcon from "@mui/icons-material/Announcement"; | ||
import { Dialog } from "@/components/Dialog"; | ||
import { stripeService } from "@/services/stripeService"; | ||
import { isConnectOnboardingCompleteStore } from "@/stores"; | ||
|
||
/** | ||
* This component is responsible for managing the state of the user's Stripe | ||
* Connect onboarding process. | ||
* | ||
* During authentication, `isConnectOnboardingCompleteStore` will be initialized | ||
* using the `user.stripeConnectAccount.detailsSubmitted` property. | ||
* | ||
* If `isConnectOnboardingComplete` is false upon successful authentication, the | ||
* user is presented with an alert dialog notifying them of the need to complete | ||
* the Connect onboarding process. | ||
* | ||
* Once the Connect onboarding flow has begun, there are two possible actions | ||
* that need to be handled: | ||
* | ||
* 1. RETURN: Upon completion of Stripe Connect onboarding process, the user | ||
* will be redirected to the Fixit route from which they originally began | ||
* the onboarding flow, along with a URL search param of "?connect-return" | ||
* (e.g., "/home/workorders?connect-return"), at which time the value of | ||
* isConnectOnboardingComplete can be flipped to `true`. | ||
* | ||
* 2. REFRESH: If the user fails to complete the onboarding flow within the | ||
* window of time allotted to the temporary portal, or if they try to | ||
* refresh the portal page, they'll be redirected to the Fixit route from | ||
* which they originally began the onboarding flow, along with a URL search | ||
* param of "?connect-refresh" (e.g., "/home/workorders?connect-refresh"). | ||
* The user can complete the onboarding flow at any time in the future. | ||
*/ | ||
export const StripeConnectOnboardingStateLayer = () => { | ||
const isConnectOnboardingComplete = isConnectOnboardingCompleteStore.useSubToStore(); | ||
const [searchParams, setSearchParams] = useSearchParams(); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
// Do nothing if isConnectOnboardingComplete is true | ||
if (!isConnectOnboardingComplete) { | ||
if (searchParams.has(STRIPE_CONNECT_URL_PARAMS.REFRESH)) { | ||
// Rm the query param and obtain new Stripe Connect onboarding portal link | ||
searchParams.delete(STRIPE_CONNECT_URL_PARAMS.REFRESH); | ||
// Update searchParams with 'REFRESH' query param removed | ||
setSearchParams(searchParams); | ||
// Get the onboarding link (run in bg without loading/error indicators) | ||
const { stripeLink } = await stripeService.getConnectOnboardingLink(); | ||
if (stripeLink) window.open(stripeLink); | ||
} else if (searchParams.has(STRIPE_CONNECT_URL_PARAMS.RETURN)) { | ||
// Rm the query param and set isConnectOnboardingComplete to true | ||
searchParams.delete(STRIPE_CONNECT_URL_PARAMS.RETURN); | ||
// Update searchParams with 'RETURN' query param removed | ||
setSearchParams(searchParams); | ||
isConnectOnboardingCompleteStore.set(true); | ||
} | ||
} | ||
})(); | ||
}, [isConnectOnboardingComplete, searchParams, setSearchParams]); | ||
|
||
// Local/internal Dialog state vars (init isDialogVisible set to !isConnectOnboardingComplete) | ||
const [hasAlertedUser, setHasAlertedUser] = useState(false); | ||
const { isDialogVisible, closeDialog } = Dialog.use(!isConnectOnboardingComplete); | ||
|
||
const handleDialogAccept = async () => { | ||
closeDialog(); | ||
setHasAlertedUser(true); | ||
const { stripeLink } = await stripeService.getConnectOnboardingLink(); | ||
if (stripeLink) window.open(stripeLink); | ||
}; | ||
|
||
const handleDialogCancel = () => { | ||
closeDialog(); | ||
setHasAlertedUser(true); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Outlet /> | ||
{!isConnectOnboardingComplete && !hasAlertedUser && ( | ||
<Dialog | ||
isVisible={isDialogVisible} | ||
title="Start Getting Paid Today!" | ||
handleAccept={handleDialogAccept} | ||
handleCancel={handleDialogCancel} | ||
acceptLabel="Complete Setup" | ||
cancelLabel="I'll do this later" | ||
> | ||
<Text> | ||
Complete your account setup to start sending and receiving payments. If you'd like to do | ||
this later - no problem! You can always manage your payment settings in the account | ||
menu. | ||
</Text> | ||
<Box style={{ display: "flex" }}> | ||
<AnnouncementIcon style={{ marginRight: "0.5rem" }} /> | ||
<Text> | ||
You won't be able to send or receive invoices until your account setup is complete. | ||
</Text> | ||
</Box> | ||
</Dialog> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
// Exported as "Component" for react-router-dom lazy loading | ||
export const Component = StripeConnectOnboardingStateLayer; | ||
|
||
export const STRIPE_CONNECT_URL_PARAMS = { | ||
RETURN: "connect-return", | ||
REFRESH: "connect-refresh", | ||
} as const; |
Oops, something went wrong.