Skip to content
Draft
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
237 changes: 214 additions & 23 deletions src/components/FormFields/RegisterAccountFields.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,226 @@
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Stack } from '@openedx/paragon';

import { FieldContainer } from '@/components/FieldContainer';
import Field from '@/components/FormFields/Field';
import { DataStoreKey } from '@/constants/checkout';
import { useCheckoutFormStore } from '@/hooks/index';

import type { UseFormReturn } from 'react-hook-form';

interface RegisterAccountFieldsProps {
form: UseFormReturn<PlanDetailsData>;
form: UseFormReturn<PlanDetailsRegisterPageData>;
}

// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const RegisterAccountFields = ({ form }: RegisterAccountFieldsProps) => (
<FieldContainer>
<div>
<h3>
<FormattedMessage
id="checkout.registerAccountField.title"
defaultMessage="Register your edX account to start the trial"
description="Title for the register account section"
const RegisterAccountFields = ({ form }: RegisterAccountFieldsProps) => {
const intl = useIntl();
const planDetailsData = useCheckoutFormStore((state) => state.formData[DataStoreKey.PlanDetails]);
const adminEmail = planDetailsData?.adminEmail;

return (
<FieldContainer>
<div className="mb-3">
<h3>
<FormattedMessage
id="checkout.registerAccountField.title"
defaultMessage="Register your edX account to start the trial"
description="Title for the register account section"
/>
</h3>
<h3 className="font-weight-light">
<FormattedMessage
id="checkout.registerAccountField.description"
defaultMessage="Your edX learner account will be granted administrator
access to manage your organization's subscription when the trial starts."
description="Description text explaining the register account purpose"
/>
</h3>
</div>
<Stack gap={1}>
<Field
form={form}
name="adminEmail"
type="email"
value={adminEmail || ''}
disabled={!!adminEmail}
readOnly={!!adminEmail}
manageState={false}
floatingLabel={intl.formatMessage({
id: 'checkout.registerAccountFields.email.floatingLabel',
defaultMessage: 'Work Email',
description: 'Floating label for the work email input field',
})}
placeholder={intl.formatMessage({
id: 'checkout.registerAccountFields.email.placeholder',
defaultMessage: 'Enter your work email',
description: 'Placeholder for the work email input field',
})}
controlClassName="mr-0 mt-3"
registerOptions={{
required: intl.formatMessage({
id: 'checkout.registerAccountFields.email.required',
defaultMessage: 'Work email is required',
description: 'Error message for required work email field',
}),
}}
/>
<Field
form={form}
name="fullName"
type="text"
manageState={false}
floatingLabel={intl.formatMessage({
id: 'checkout.registerAccountFields.fullName.floatingLabel',
defaultMessage: 'Full Name',
description: 'Floating label for the full name input field',
})}
placeholder={intl.formatMessage({
id: 'checkout.registerAccountFields.fullName.placeholder',
defaultMessage: 'Enter your full name',
description: 'Placeholder for the full name input field',
})}
controlClassName="mr-0 mt-3"
registerOptions={{
required: intl.formatMessage({
id: 'checkout.registerAccountFields.fullName.required',
defaultMessage: 'Full name is required',
description: 'Error message for required full name field',
}),
}}
/>
<Field
form={form}
name="username"
type="text"
manageState={false}
floatingLabel={intl.formatMessage({
id: 'checkout.registerAccountFields.username.floatingLabel',
defaultMessage: 'Public Username',
description: 'Floating label for the username input field',
})}
placeholder={intl.formatMessage({
id: 'checkout.registerAccountFields.username.placeholder',
defaultMessage: 'Enter your desired username',
description: 'Placeholder for the username input field',
})}
controlClassName="mr-0 mt-3"
registerOptions={{
required: intl.formatMessage({
id: 'checkout.registerAccountFields.username.required',
defaultMessage: 'Username is required',
description: 'Error message for required username field',
}),
}}
/>
<Field
form={form}
name="password"
type="password"
manageState={false}
floatingLabel={intl.formatMessage({
id: 'checkout.registerAccountFields.password.floatingLabel',
defaultMessage: 'Password',
description: 'Floating label for the password input field',
})}
placeholder={intl.formatMessage({
id: 'checkout.registerAccountFields.password.placeholder',
defaultMessage: 'Enter your password',
description: 'Placeholder for the password input field',
})}
controlClassName="mr-0 mt-3"
registerOptions={{
required: intl.formatMessage({
id: 'checkout.registerAccountFields.password.required',
defaultMessage: 'Password is required',
description: 'Error message for required password field',
}),
}}
/>
<Field
form={form}
name="confirmPassword"
type="password"
manageState={false}
floatingLabel={intl.formatMessage({
id: 'checkout.registerAccountFields.confirmPassword.floatingLabel',
defaultMessage: 'Confirm Password',
description: 'Floating label for the confirm password input field',
})}
placeholder={intl.formatMessage({
id: 'checkout.registerAccountFields.confirmPassword.placeholder',
defaultMessage: 'Confirm your password',
description: 'Placeholder for the confirm password input field',
})}
controlClassName="mr-0 mt-3"
registerOptions={{
required: intl.formatMessage({
id: 'checkout.registerAccountFields.confirmPassword.required',
defaultMessage: 'Please confirm your password',
description: 'Error message for required confirm password field',
}),
}}
/>
</h3>
<h3 className="font-weight-light">
<FormattedMessage
id="checkout.registerAccountField.description"
defaultMessage="Your edX learner account will be granted administrator
access to manage your organization's subscription when the trial starts."
description="Description text explaining the register account purpose"
<Field
form={form}
name="country"
type="select"
manageState={false}
options={[
{
value: 'US',
label: 'United States',
},
{
value: 'CA',
label: 'Canada',
},
{
value: 'GB',
label: 'United Kingdom',
},
{
value: 'AU',
label: 'Australia',
},
{
value: 'DE',
label: 'Germany',
},
{
value: 'FR',
label: 'France',
},
{
value: 'IN',
label: 'India',
},
{
value: 'JP',
label: 'Japan',
},
]}
floatingLabel={intl.formatMessage({
id: 'checkout.registerAccountFields.country.floatingLabel',
defaultMessage: 'Country',
description: 'Floating label for the country dropdown field',
})}
placeholder={intl.formatMessage({
id: 'checkout.registerAccountFields.country.placeholder',
defaultMessage: 'Select a country',
description: 'Placeholder for the country dropdown field',
})}
controlClassName="mr-0 mt-3"
registerOptions={{
required: intl.formatMessage({
id: 'checkout.registerAccountFields.country.required',
defaultMessage: 'Country is required',
description: 'Error message for required country field',
}),
}}
/>
</h3>
</div>
</FieldContainer>
);
</Stack>
</FieldContainer>
);
};

export default RegisterAccountFields;
86 changes: 86 additions & 0 deletions src/components/FormFields/tests/RegisterAccountFields.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { zodResolver } from '@hookform/resolvers/zod';
import { render, screen } from '@testing-library/react';
import { useForm } from 'react-hook-form';

import { PlanDetailsRegisterPageSchema } from '@/constants/checkout';

import RegisterAccountFields from '../RegisterAccountFields';

// Mock constraints for testing
const mockConstraints: CheckoutContextFieldConstraints = {
quantity: {
min: 1,
max: 1000,
minLength: 1,
maxLength: 10,
pattern: '^[0-9]+$',
},
enterpriseSlug: {
min: 1,
max: 50,
minLength: 2,
maxLength: 50,
pattern: '^[a-z0-9-]+$',
},
};

// Create a test wrapper component
const TestWrapper = () => {
const form = useForm({
resolver: zodResolver(PlanDetailsRegisterPageSchema(mockConstraints)),
defaultValues: {
adminEmail: '',
fullName: '',
username: '',
password: '',
confirmPassword: '',
country: '',
},
});

return (
<IntlProvider locale="en">
<RegisterAccountFields form={form} />
</IntlProvider>
);
};

describe('RegisterAccountFields', () => {
it('renders all registration form fields', () => {
render(<TestWrapper />);

// Check that all fields are rendered
expect(screen.getByLabelText(/work email/i)).toBeDefined();
expect(screen.getByLabelText(/full name/i)).toBeDefined();
expect(screen.getByLabelText(/public username/i)).toBeDefined();
expect(screen.getByLabelText(/^password$/i)).toBeDefined();
expect(screen.getByLabelText(/confirm password/i)).toBeDefined();
expect(screen.getByLabelText(/country/i)).toBeDefined();
});

it('renders registration title and description', () => {
render(<TestWrapper />);

expect(screen.getByText(/register your edx account to start the trial/i)).toBeDefined();
expect(screen.getByText(/your edx learner account will be granted administrator access/i)).toBeDefined();
});

it('includes country options', () => {
render(<TestWrapper />);

const countrySelect = screen.getByLabelText(/country/i);
expect(countrySelect).toBeDefined();
expect(countrySelect.tagName).toBe('SELECT');
});

it('has proper field types', () => {
render(<TestWrapper />);

expect(screen.getByLabelText(/work email/i).getAttribute('type')).toBe('email');
expect(screen.getByLabelText(/^password$/i).getAttribute('type')).toBe('password');
expect(screen.getByLabelText(/confirm password/i).getAttribute('type')).toBe('password');
expect(screen.getByLabelText(/full name/i).getAttribute('type')).toBe('text');
expect(screen.getByLabelText(/public username/i).getAttribute('type')).toBe('text');
});
});
Loading
Loading