Skip to content

Commit 533650a

Browse files
feat: add Essentials plan details form with license user validation
1 parent 475d5d3 commit 533650a

File tree

11 files changed

+418
-49
lines changed

11 files changed

+418
-49
lines changed

src/components/Stepper/CheckoutStepperContainer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const Steps = (): ReactElement => (
1717

1818
const CheckoutStepperContainer = (): ReactElement => {
1919
const { currentStepKey, currentSubstepKey } = useCurrentStep();
20-
2120
useEffect(() => {
2221
const preventUnload = (e: BeforeUnloadEvent) => {
2322
if (currentSubstepKey !== CheckoutSubstepKey.Success) {

src/components/Stepper/StepperContent/PlanDetailsContent.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppContext } from '@edx/frontend-platform/react';
22
import { useContext } from 'react';
3+
import { useMatch } from 'react-router-dom';
34

45
import { AuthenticatedUserField, LicensesField, NameAndEmailFields } from '@/components/FormFields';
56
import { PriceAlert } from '@/components/plan-details-pages/PriceAlert';
@@ -15,6 +16,9 @@ interface PlanDetailsContentProps {
1516
form: UseFormReturn<PlanDetailsData>;
1617
}
1718

19+
// Hook that determines whether the current route is part of the Essentials flow.
20+
const useIsEssentialsRoute = () => !!useMatch('/essentials/*');
21+
1822
/**
1923
* Renders the content for the Plan Details step, including:
2024
* - Price alert
@@ -26,9 +30,10 @@ interface PlanDetailsContentProps {
2630
*/
2731
const PlanDetailsContent = ({ form }: PlanDetailsContentProps) => {
2832
const { authenticatedUser }: AppContextValue = useContext(AppContext);
33+
const isEssentials = useIsEssentialsRoute();
2934
return (
3035
<>
31-
<PriceAlert />
36+
{!isEssentials && <PriceAlert />}
3237
<LicensesField form={form} />
3338
{authenticatedUser
3439
? (

src/components/app/routes/loaders/checkoutStepperLoader.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,41 @@ const makeCheckoutStepperLoader: MakeRouteLoaderFunctionWithQueryClient = functi
201201
};
202202

203203
export default makeCheckoutStepperLoader;
204+
205+
export const makeEssentialsCheckoutStepperLoader = (queryClient?: QueryClient) => {
206+
const baseLoader = makeCheckoutStepperLoader(queryClient);
207+
return async function essentialsCheckoutStepperLoader(args: any) {
208+
try {
209+
// Delegate to the original checkout stepper loader.
210+
// If it returns normally, just pass the value through.
211+
// eslint-disable-next-line @typescript-eslint/return-await
212+
return await baseLoader(args);
213+
} catch (error: any) {
214+
// React Router implements redirects in loaders by throwing a `Response`
215+
// whose `Location` header indicates the redirect target.
216+
if (error instanceof Response) {
217+
const { status } = error;
218+
const location = error.headers.get('Location');
219+
// Only consider typical redirect statuses with an absolute, non-Essentials path.
220+
if (
221+
location
222+
&& (status === 301 || status === 302 || status === 303 || status === 307 || status === 308)
223+
&& location.startsWith('/')
224+
&& !location.startsWith('/essentials/')
225+
) {
226+
const newLocation = `/essentials${location}`;
227+
const headers = new Headers(error.headers);
228+
headers.set('Location', newLocation);
229+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
230+
throw new Response(error.body, {
231+
status: error.status,
232+
statusText: error.statusText,
233+
headers,
234+
});
235+
}
236+
}
237+
// For non-redirect errors or already-correct Essentials paths, rethrow unchanged.
238+
throw error;
239+
}
240+
};
241+
};

src/components/app/routes/loaders/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
*/
66
export { default as makeRootLoader } from './rootLoader';
77
export { default as makeCheckoutStepperLoader } from './checkoutStepperLoader';
8+
export { makeEssentialsCheckoutStepperLoader } from './checkoutStepperLoader';

src/components/app/routes/loaders/rootLoader.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,8 @@ const makeRootLoader = (
122122
* This check happens AFTER feature flag validation.
123123
*/
124124
const isCheckoutRoute = !Object.values(EssentialsPageRoute).some(route => isPathMatch(currentPath, route));
125-
126125
if (!isCheckoutRoute) {
127-
return null;
126+
// return null; Allowing essential routes to participate checkout intent logic
128127
}
129128

130129
// Fetch basic info about authenticated user from JWT token, and also hydrate it with additional

0 commit comments

Comments
 (0)