Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/components/FormFields/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ import { Form } from '@openedx/paragon';
import {
CheckCircle, Error as ErrorIcon,
} from '@openedx/paragon/icons';
import { camelCase } from 'lodash-es';
import {
forwardRef,
useCallback,
useImperativeHandle,
useRef,
} from 'react';

import { CheckoutStepKey } from '@/components/Stepper/constants';
import useCheckoutFormStore from '@/hooks/useCheckoutFormStore';
import useCurrentStep from '@/hooks/useCurrentStep';

Expand Down Expand Up @@ -103,9 +101,9 @@ const DefaultFormControlBase = <T extends FieldValues>(
) => {
const intl = useIntl();
const controlRef = useRef<FormControlElement | null>(null);
const currentStep = camelCase(useCurrentStep()!);
const formData = useCheckoutFormStore((state) => state.formData[currentStep]);
const currentValue = formData?.[name as CheckoutStepKey];
const { currentStep } = useCurrentStep();
const formData = useCheckoutFormStore((state) => state.formData[currentStep!]);
const currentValue = formData?.[name];
const setFormData = useCheckoutFormStore((state) => state.setFormData);
const { register } = form;
const { onChange } = registerOptions;
Expand All @@ -117,7 +115,7 @@ const DefaultFormControlBase = <T extends FieldValues>(
...registerOptions,
onChange: (event: React.ChangeEvent<FormControlElement>) => {
// @ts-ignore
setFormData(currentStep, {
setFormData(step, {
...formData,
[name]: event.target.value,
});
Expand Down
28 changes: 11 additions & 17 deletions src/components/Stepper/Steps/hooks/useStepperContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useParams } from 'react-router';

import {
AccountDetailsContent,
BillingDetailsContent,
Expand All @@ -8,24 +6,20 @@ import {
PlanDetailsLoginContent,
PlanDetailsRegisterContent,
} from '@/components/Stepper/StepperContent';
import { determineStepperStep } from '@/components/Stepper/utils';
import { CheckoutStepKey, CheckoutSubstepKey } from '@/constants/checkout';
import { useCurrentPage } from '@/hooks/useCurrentStep';

const StepperContent = {
[CheckoutStepKey.PlanDetails]: PlanDetailsContent,
[CheckoutSubstepKey.Login]: PlanDetailsLoginContent,
[CheckoutSubstepKey.Register]: PlanDetailsRegisterContent,
[CheckoutStepKey.AccountDetails]: AccountDetailsContent,
[CheckoutStepKey.BillingDetails]: BillingDetailsContent,
[CheckoutSubstepKey.Success]: BillingDetailsSuccessContent,
fallback: () => {},
};
const StepperContentByPage = {
PlanDetails: PlanDetailsContent,
PlanDetailsLogin: PlanDetailsLoginContent,
PlanDetailsRegister: PlanDetailsRegisterContent,
AccountDetails: AccountDetailsContent,
BillingDetails: BillingDetailsContent,
BillingDetailsSuccess: BillingDetailsSuccessContent,
} as { [K in CheckoutPage]: React.FC };

const useStepperContent = () => {
const params = useParams<{ step: CheckoutStepKey, substep: CheckoutSubstepKey }>();
const currentStep = determineStepperStep(params);
// @ts-ignore
return StepperContent[currentStep];
const currentPage = useCurrentPage();
return currentPage ? StepperContentByPage[currentPage] : undefined;
};

export default useStepperContent;
8 changes: 6 additions & 2 deletions src/components/Stepper/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import {
AccountDetailsSchema,
BillingDetailsSchema,
CheckoutPageDetails,
CheckoutStepByKey,
CheckoutStepKey,
CheckoutStepperPath,
CheckoutSubstepByKey,
CheckoutSubstepKey,
PlanDetailsLoginSchema,
PlanDetailsRegistrationSchema,
Expand All @@ -21,8 +23,10 @@ export const authenticatedSteps = [
export {
AccountDetailsSchema,
BillingDetailsSchema,
CheckoutPageDetails,
CheckoutStepByKey,
CheckoutStepKey,
CheckoutStepperPath,
CheckoutSubstepByKey,
CheckoutSubstepKey,
PlanDetailsLoginSchema,
PlanDetailsRegistrationSchema,
Expand Down
130 changes: 0 additions & 130 deletions src/components/Stepper/utils.ts
Original file line number Diff line number Diff line change
@@ -1,130 +0,0 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

import { CheckoutStepKey, CheckoutSubstepKey } from '@/constants/checkout';

/**
* Define formatted messages for page titles
* These correspond to the FormattedMessage components used in the page h1 elements
*/
export const titleMessages = defineMessages({
[CheckoutStepKey.PlanDetails]: {
id: 'checkout.planDetails.title',
defaultMessage: 'Plan Details',
description: 'Title for the plan details step',
},
[CheckoutSubstepKey.Login]: {
id: 'checkout.planDetailsLogin.title',
defaultMessage: 'Log in to your account',
description: 'Title for the login page in the plan details step',
},
[CheckoutSubstepKey.Register]: {
id: 'checkout.planDetailsRegistration.title',
defaultMessage: 'Create your Account',
description: 'Title for the registration page in the plan details step',
},
[CheckoutStepKey.AccountDetails]: {
id: 'checkout.accountDetails.title',
defaultMessage: 'Account Details',
description: 'Title for the account details step',
},
[CheckoutStepKey.BillingDetails]: {
id: 'checkout.billingDetails.title',
defaultMessage: 'Billing Details',
description: 'Title for the billing details step',
},
[CheckoutSubstepKey.Success]: {
id: 'checkout.billingDetailsSuccess.title',
defaultMessage: 'Thank you, {firstName}.',
description: 'Title for the success page',
},
});

/**
* Define formatted messages for button text
* These correspond to the FormattedMessage components used in the button elements
*/
export const buttonMessages = defineMessages({
[CheckoutStepKey.PlanDetails]: {
id: 'checkout.planDetails.continue',
defaultMessage: 'Continue',
description: 'Button label for the next step in the plan details step',
},
[CheckoutSubstepKey.Login]: {
id: 'checkout.registrationPage.register',
defaultMessage: 'Sign in',
description: 'Button label to register a new user in the plan details step',
},
[CheckoutSubstepKey.Register]: {
id: 'checkout.registrationPage.register',
defaultMessage: 'Register',
description: 'Button label to register a new user in the plan details step',
},
[CheckoutStepKey.AccountDetails]: {
id: 'checkout.accountDetails.continue',
defaultMessage: 'Continue',
description: 'Button to go to the next page',
},
[CheckoutStepKey.BillingDetails]: {
id: 'checkout.billingDetails.purchaseNow',
defaultMessage: 'Purchase Now',
description: 'Button to purchase the subscription product',
},
[CheckoutSubstepKey.Success]: {
id: 'checkout.billingDetailsSuccess.fallback',
},
});

const determineStepperStep = (params: { step?: CheckoutStepKey, substep?: CheckoutSubstepKey }) => {
const { step, substep } = params;

switch (step) {
case CheckoutStepKey.PlanDetails:
switch (substep) {
case CheckoutSubstepKey.Login:
return CheckoutSubstepKey.Login;
case CheckoutSubstepKey.Register:
return CheckoutSubstepKey.Register;
default:
return CheckoutStepKey.PlanDetails;
}
case CheckoutStepKey.AccountDetails:
return CheckoutStepKey.AccountDetails;
case CheckoutStepKey.BillingDetails:
switch (substep) {
case CheckoutSubstepKey.Success:
return CheckoutSubstepKey.Success;
default:
return CheckoutStepKey.BillingDetails;
}
default:
return null;
}
};

/**
* Determines the appropriate formatted message for the helmet title based on the current step and substep
*
* @param params - Object containing the current step and substep
* @returns The formatted message object to use for the helmet title
*/
const determineStepperTitleText = (params: { step?: CheckoutStepKey, substep?: CheckoutSubstepKey }) => {
const { step, substep } = params;
return titleMessages[determineStepperStep({ step, substep })];
};

/**
* Determines the appropriate formatted message for the button text based on the current step and substep
*
* @param params - Object containing the current step and substep
* @returns The formatted message object to use for the button text
*/
const determineStepperButtonText = (params: { step?: CheckoutStepKey, substep?: CheckoutSubstepKey }) => {
const { step, substep } = params;
return buttonMessages[determineStepperStep({ step, substep })];
};

export {
determineStepperTitleText,
determineStepperButtonText,
determineStepperStep,
};
19 changes: 8 additions & 11 deletions src/components/plan-details-pages/PlanDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,24 @@ import {
} from '@openedx/paragon';
import { Helmet } from 'react-helmet';
import { useForm } from 'react-hook-form';
import { useParams } from 'react-router';
import { useNavigate } from 'react-router-dom';

import { PriceAlert } from '@/components/PriceAlert';
import useStepperContent from '@/components/Stepper/Steps/hooks/useStepperContent';
import { determineStepperButtonText, determineStepperStep, determineStepperTitleText } from '@/components/Stepper/utils';
import {
CheckoutPageDetails,
CheckoutStepKey,
CheckoutStepperPath,
CheckoutSubstepKey,
PlanDetailsSchema,
} from '@/constants/checkout';
import useCheckoutFormStore from '@/hooks/useCheckoutFormStore';
import { useCurrentPageDetails } from '@/hooks/useCurrentStep';

import '../Stepper/Steps/css/PriceAlert.css';

const PlanDetailsPage: React.FC = () => {
// TODO: Example usage of retrieving context data and validation
// const bffContext = useBFFContext();
// const bffValidation = useBFFValidation(baseValidation);
const { step, substep } = useParams<{ step: CheckoutStepKey, substep: CheckoutSubstepKey }>();
const intl = useIntl();
const planFormData = useCheckoutFormStore((state) => state.formData.planDetails);
const formData = useCheckoutFormStore((state) => state.formData);
Expand Down Expand Up @@ -57,29 +54,29 @@ const PlanDetailsPage: React.FC = () => {
// TODO: replace with an authenticatedUser
if (!isAuthenticated) {
if (randomExistingEmail) {
navigate(CheckoutStepperPath.LoginRoute);
navigate(CheckoutPageDetails.PlanDetailsLogin.route);
} else {
navigate(CheckoutStepperPath.RegisterRoute);
navigate(CheckoutPageDetails.PlanDetailsRegister.route);
}
return;
}

if (isAuthenticated) {
navigate(CheckoutStepperPath.AccountDetailsRoute);
navigate(CheckoutPageDetails.AccountDetails.route);
}
};

const { title: pageTitle, buttonMessage: stepperActionButtonMessage } = useCurrentPageDetails();
const StepperContent = useStepperContent();

const eventKey = CheckoutStepKey.PlanDetails;
determineStepperStep({ step, substep });
return (
<Form onSubmit={handleSubmit(onSubmit)}>
<Helmet title="Plan Details" />
<Stack gap={4}>
<Stepper.Step eventKey={eventKey} title="Plan Details">
<h1 className="mb-5 text-center" data-testid="stepper-title">
{intl.formatMessage(determineStepperTitleText({ step, substep }))}
{intl.formatMessage(pageTitle)}
</h1>
<Stack gap={4}>
<PriceAlert />
Expand All @@ -93,7 +90,7 @@ const PlanDetailsPage: React.FC = () => {
disabled={!isValid}
data-testid="stepper-submit-button"
>
{intl.formatMessage(determineStepperButtonText({ step, substep }))}
{intl.formatMessage(stepperActionButtonMessage)}
</Button>
</Stepper.ActionRow>
</Stack>
Expand Down
Loading
Loading