diff --git a/astro.config.mjs b/astro.config.mjs index afb9fd010..630612071 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -677,6 +677,18 @@ async function config() { collapsed: true, items: [ { label: 'Overview', link: '/dropins-b2b/' }, + { + label: 'Company Management', + collapsed: true, + items: [ + { label: 'Overview', link: '/dropins-b2b/company/' }, + { label: 'Installation', link: '/dropins-b2b/company/installation/' }, + { label: 'Initialization', link: '/dropins-b2b/company/initialization/' }, + { label: 'Functions', link: '/dropins-b2b/company/functions/' }, + { label: 'Events', link: '/dropins-b2b/company/events/' }, + { label: 'Dictionary', link: '/dropins-b2b/company/dictionary/' }, + ], + }, { label: 'B2B Containers', collapsed: true, diff --git a/src/content/docs/dropins-b2b/company/dictionary.mdx b/src/content/docs/dropins-b2b/company/dictionary.mdx new file mode 100644 index 000000000..23bfa9224 --- /dev/null +++ b/src/content/docs/dropins-b2b/company/dictionary.mdx @@ -0,0 +1,282 @@ +--- +title: Company dictionary +description: Learn how to customize the default dictionary for the Company drop-in component. +sidebar: + label: Dictionary + order: 7 +--- + +import CodeInclude from '@components/CodeInclude.astro'; +import CodeImport from '@components/CodeImport.astro'; + +The Company drop-in component includes a comprehensive dictionary for internationalization and text customization. The default dictionary contains all labels, messages, validation text, and user interface strings used throughout the company management interface. You can customize these values or add support for additional languages. + +## Default keys and values + +The dictionary file for the Company drop-in is located at: +`@dropins/storefront-company-management/i18n/en_US.json` + +```json +{ + "Company": { + "shared": { + "fields": { + "companyName": "Company Name", + "email": "Email", + "legalName": "Legal Name", + "vatTaxId": "VAT/Tax ID", + "resellerId": "Reseller ID", + "legalAddress": "Legal Address", + "streetAddress": "Street Address", + "city": "City", + "country": "Country", + "stateProvince": "State/Province", + "zipPostalCode": "ZIP/Postal Code", + "phoneNumber": "Phone Number", + "status": "Status", + "region": "Region", + "postalCode": "Postal Code", + "jobTitle": "Job Title", + "workPhoneNumber": "Work Phone Number", + "userRole": "User Role" + }, + "buttons": { + "edit": "Edit", + "cancel": "Cancel", + "save": "Save Changes", + "saving": "Saving...", + "close": "Close", + "confirm": "Confirm" + }, + "validation": { + "required": "This field is required", + "invalidEmail": "Please enter a valid email address", + "companyNameRequired": "Company name is required", + "emailRequired": "Email is required", + "emailNotAvailable": "This email is already used by another company", + "phoneInvalid": "Please enter a valid phone number", + "postalCodeInvalid": "Please enter a valid postal code", + "companyNameLengthError": "Company name must not exceed 40 characters", + "legalNameLengthError": "Legal name must not exceed 80 characters", + "vatTaxIdLengthError": "VAT/Tax ID must not exceed 40 characters", + "resellerIdLengthError": "Reseller ID must not exceed 40 characters" + }, + "messages": { + "loading": "Loading...", + "noData": "No data available", + "error": "An error occurred", + "success": "Operation completed successfully" + }, + "ariaLabels": { + "editButton": "Edit company profile", + "cancelButton": "Cancel editing", + "saveButton": "Save company profile changes", + "closeButton": "Close dialog" + } + }, + "CompanyProfile": { + "containerTitle": "Company Profile", + "editCompanyProfile": { + "containerTitle": "Edit Company Profile", + "companySuccess": "Company profile updated successfully", + "companyError": "Failed to update company profile", + "buttonSecondary": "Cancel", + "buttonPrimary": "Save Changes" + }, + "companyProfileCard": { + "noDataMessage": "Company profile not available. Please contact your administrator.", + "contacts": "Contacts", + "companyAdministrator": "Company Administrator", + "salesRepresentative": "Sales Representative", + "paymentInformation": "Payment Information", + "availablePaymentMethods": "Available Payment Methods", + "shippingInformation": "Shipping Information", + "availableShippingMethods": "Available Shipping Methods", + "noPaymentMethods": "This company has no payment methods. Please contact store administrator.", + "noShippingMethods": "This company has no shipping methods. Please contact store administrator.", + "companyDetails": "Company Details", + "addressInformation": "Address Information" + }, + "messages": { + "loadError": "Failed to load company profile", + "updateError": "Failed to update company profile", + "loadingProfile": "Loading company profile...", + "savingProfile": "Saving company profile..." + } + }, + "FormText": { + "requiredFieldError": "This is a required field.", + "numericError": "Only numeric values are allowed.", + "alphaNumWithSpacesError": "Only alphanumeric characters and spaces are allowed.", + "alphaNumericError": "Only alphanumeric characters are allowed.", + "alphaError": "Only alphabetic characters are allowed.", + "emailError": "Please enter a valid email address.", + "phoneError": "Please enter a valid phone number.", + "postalCodeError": "Please enter a valid postal code.", + "lengthTextError": "Text length must be between {min} and {max} characters.", + "companyNameLengthError": "Company name must be between {min} and {max} characters." + } + } +} +``` + +## Dictionary structure + +The dictionary is organized into logical sections: + +### Shared section + +Contains common elements used across multiple components: +- **fields**: Labels for form fields and data display +- **buttons**: Text for interactive elements +- **validation**: Error messages for form validation +- **messages**: General status and informational messages +- **ariaLabels**: Accessibility labels for screen readers + +### CompanyProfile section + +Contains text specific to the CompanyProfile container: +- **containerTitle**: Main container heading +- **editCompanyProfile**: Edit form specific text +- **companyProfileCard**: Profile card display text +- **messages**: Component-specific status messages + +### FormText section + +Contains validation messages and form-related text: +- Field validation error messages +- Input format requirements +- Length validation messages with placeholder support + +## Customizing the dictionary + +### Method 1: Initialize with custom dictionary + +```js +import { initialize } from '@dropins/storefront-company-management/api'; + +await initialize({ + langDefinitions: { + en_US: { + 'Company.shared.fields.companyName': 'Business Name', + 'Company.shared.fields.email': 'Business Email', + 'Company.CompanyProfile.containerTitle': 'Business Profile', + 'Company.shared.buttons.edit': 'Modify', + 'Company.shared.buttons.save': 'Update Changes' + } + } +}); +``` + +### Method 2: Override specific keys + +```js +// Override individual dictionary keys +const customDictionary = { + 'Company.shared.fields.companyName': 'Organization Name', + 'Company.shared.fields.legalName': 'Legal Business Name', + 'Company.CompanyProfile.companyProfileCard.contacts': 'Contact Information', + 'Company.shared.validation.required': 'This field cannot be empty' +}; + +await initialize({ + langDefinitions: { + en_US: customDictionary + } +}); +``` + +## Adding new languages + +### Spanish (es_ES) example + +```js +const spanishDictionary = { + 'Company.shared.fields.companyName': 'Nombre de la Empresa', + 'Company.shared.fields.email': 'Correo Electrónico', + 'Company.shared.fields.legalName': 'Nombre Legal', + 'Company.shared.fields.vatTaxId': 'NIF/CIF', + 'Company.shared.fields.legalAddress': 'Dirección Legal', + 'Company.shared.fields.city': 'Ciudad', + 'Company.shared.fields.country': 'País', + 'Company.shared.fields.postalCode': 'Código Postal', + 'Company.shared.buttons.edit': 'Editar', + 'Company.shared.buttons.cancel': 'Cancelar', + 'Company.shared.buttons.save': 'Guardar Cambios', + 'Company.shared.validation.required': 'Este campo es obligatorio', + 'Company.shared.validation.invalidEmail': 'Ingrese una dirección de correo válida', + 'Company.CompanyProfile.containerTitle': 'Perfil de la Empresa' +}; + +await initialize({ + langDefinitions: { + es_ES: spanishDictionary + } +}); +``` + +### French (fr_FR) example + +```js +const frenchDictionary = { + 'Company.shared.fields.companyName': 'Nom de l\'Entreprise', + 'Company.shared.fields.email': 'Adresse E-mail', + 'Company.shared.fields.legalName': 'Raison Sociale', + 'Company.shared.fields.vatTaxId': 'Numéro de TVA', + 'Company.shared.fields.legalAddress': 'Adresse Légale', + 'Company.shared.fields.city': 'Ville', + 'Company.shared.fields.country': 'Pays', + 'Company.shared.fields.postalCode': 'Code Postal', + 'Company.shared.buttons.edit': 'Modifier', + 'Company.shared.buttons.cancel': 'Annuler', + 'Company.shared.buttons.save': 'Enregistrer', + 'Company.shared.validation.required': 'Ce champ est obligatoire', + 'Company.CompanyProfile.containerTitle': 'Profil de l\'Entreprise' +}; + +await initialize({ + langDefinitions: { + fr_FR: frenchDictionary + } +}); +``` + +## Dynamic text with placeholders + +Some dictionary entries support placeholders for dynamic content: + +```js +// Dictionary entry with placeholders +'Company.FormText.lengthTextError': 'Text length must be between {min} and {max} characters.' + +// Usage in validation +const errorMessage = getText('Company.FormText.lengthTextError', { min: 1, max: 40 }); +// Result: "Text length must be between 1 and 40 characters." +``` + +## Best practices + +### Naming conventions +- Use descriptive, hierarchical keys +- Group related text under common prefixes +- Use camelCase for key names +- Include context in key names (e.g., `companyProfileCard.noDataMessage`) + +### Translation guidelines +- Keep text concise and clear +- Consider character length differences between languages +- Maintain consistent terminology across the interface +- Test with longer text to ensure UI layout remains intact + +### Accessibility considerations +- Provide clear, descriptive aria labels +- Use plain language for error messages +- Ensure button text clearly describes the action +- Include context for screen reader users + +:::note +```js +import dictionary from '@dropins/storefront-company-management/i18n/en_US.json?raw'; +``` +::: + diff --git a/src/content/docs/dropins-b2b/company/events.mdx b/src/content/docs/dropins-b2b/company/events.mdx new file mode 100644 index 000000000..c78442669 --- /dev/null +++ b/src/content/docs/dropins-b2b/company/events.mdx @@ -0,0 +1,87 @@ +--- +title: Company Management Data & Events +description: Learn about the events used by the Company Management and the data available within the events. +sidebar: + label: Events + order: 5 +--- + +import { Aside } from '@astrojs/starlight/components'; +import TableWrapper from '@components/TableWrapper.astro'; + +The **Company Management** drop-in uses the [Event Bus](/sdk/reference/events/) to emit and listen to events for communication between drop-ins and external integrations. For common events shared across multiple drop-ins (such as `locale`, `error`, `authenticated`, etc.), see the Common Events Reference. + +## Events reference + +{/* EVENTS_TABLE_START */} + + +| Event | Direction | Description | +|-------|-----------|-------------| +| [company/updated](#companyupdated-emits) | Emits | Emitted when the component state is updated | +| [companyContext/changed](#companycontextchanged-listens) | Listens | Fired by Company Context (`companyContext`) when a change occurs | + + +{/* EVENTS_TABLE_END */} + +## Event details + +The following sections provide detailed information about each event, including its direction, data payload structure, and usage examples. + +### `company/updated` (emits) + +Emitted when the component state is updated + +#### Data payload + +```typescript +{ company?: any; message?: string; error?: any } +``` + +| Property | Type | Description | +|----------|------|-------------| +| `company` | `any` (optional) | See type definition in source code | +| `message` | `string` (optional) | See type definition in source code | +| `error` | `any` (optional) | See type definition in source code | + +#### Usage + +Listen to this event in your storefront: + +```javascript +import { events } from '@dropins/tools/event-bus.js'; + +const companyUpdatedListener = events.on('company/updated', (data) => { + console.log('company/updated event received:', data); + // Add your custom logic here +}); + +// Later, when you want to stop listening +companyUpdatedListener.off(); +``` + +### `companyContext/changed` (listens) + +Fired by Company Context (`companyContext`) when a change occurs + +#### Data payload + +```typescript +string | null | undefined +``` + +#### Usage + +Listen to this event in your storefront: + +```javascript +import { events } from '@dropins/tools/event-bus.js'; + +const companyContextChangedListener = events.on('companyContext/changed', (data) => { + console.log('companyContext/changed event received:', data); + // Add your custom logic here +}); + +// Later, when you want to stop listening +companyContextChangedListener.off(); +``` diff --git a/src/content/docs/dropins-b2b/company/functions.mdx b/src/content/docs/dropins-b2b/company/functions.mdx new file mode 100644 index 000000000..cc0c81329 --- /dev/null +++ b/src/content/docs/dropins-b2b/company/functions.mdx @@ -0,0 +1,509 @@ +--- +title: Company functions +description: Learn about the API functions provided by the Company drop-in component. +sidebar: + label: Functions + order: 6 +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 2 +--- + +import Aside from '@components/Aside.astro'; +import CodeInclude from '@components/CodeInclude.astro'; + +The Company drop-in component provides API functions that allow developers to retrieve and manage B2B company information dynamically. These functions are permissions-aware and integrate seamlessly with Adobe Commerce B2B features. + +## allowCompanyRegistration + +The `allowCompanyRegistration` function checks if company registration is allowed in the Adobe Commerce store configuration. This determines whether new companies can be registered through the storefront. The function uses the [`storeConfig` query](https://developer.adobe.com/commerce/webapi/graphql/schema/store/queries/store-config/) to fetch the `b2b/company_registration/allow` setting. + +```ts +export const allowCompanyRegistration = async (): Promise +``` + +### Returns + +Returns a promise that resolves to a boolean indicating if company registration is allowed. + +### Usage + +```js +import { allowCompanyRegistration } from '@dropins/storefront-company-management/api'; + +const canRegister = await allowCompanyRegistration(); +if (canRegister) { + console.log('Company registration is enabled'); + showRegistrationForm(); +} else { + console.log('Company registration is disabled'); + hideRegistrationForm(); +} +``` + +## companyEnabled + +The `companyEnabled` function checks if B2B company features are enabled in the Adobe Commerce store configuration. This is the primary function to determine if the store has B2B capabilities. It calls the [`storeConfig` query](https://developer.adobe.com/commerce/webapi/graphql/schema/store/queries/store-config/) to fetch the `b2b/general/enabled` setting. + +If the API call succeeds, it returns `{ companyEnabled: true }`. On failure, it returns `{ companyEnabled: false, error: 'Company functionality not available' }`. + + +```ts +export const companyEnabled = async (): Promise +``` + +### Returns + +Returns a promise that resolves to a boolean indicating if company features are enabled. + +### Usage + +```js +import { companyEnabled } from '@dropins/storefront-company-management/api'; + +try { + const isEnabled = await companyEnabled(); + if (isEnabled) { + console.log('Company features are enabled'); + initializeCompanyFeatures(); + } else { + console.log('Company features are disabled'); + } +} catch (error) { + console.error('Failed to check company status:', error.message); +} +``` + +## createCompany + +The `createCompany` function creates a new company registration. This function is used when a customer wants to register their business as a company. + +The form data must include: + +- Company information - Basic company details (name, email, legal name, tax IDs) +- Legal address - Complete business address with country/region validation +- Company administrator - Primary contact and administrator setup + +This function calls the [`createCompany` mutation](https://developer.adobe.com/commerce/webapi/graphql/schema/b2b/company/mutations/create/). + +```ts +export const createCompany = async ( + companyData: CreateCompanyDto +): Promise +``` + +Parameter | Type | Req? | Description +--- | --- | --- | --- +`companyData` | CreateCompanyDto | Yes | Company registration data including name, email, address, and admin information. + +### Returns + +Returns a promise that resolves to the newly created `CompanyModel` object. + +### Usage + +```js +import { createCompany } from '@dropins/storefront-company-management/api'; + +try { + const newCompany = await createCompany({ + name: 'Acme Corporation', + email: 'contact@acme.com', + legalName: 'Acme Corporation LLC', + legalAddress: { + street: ['123 Business Ave'], + city: 'San Francisco', + region: { region: 'California', regionCode: 'CA' }, + countryCode: 'US', + postcode: '94105', + telephone: '+1-555-123-4567' + } + }); + + console.log('Company created successfully:', newCompany.name); +} catch (error) { + console.error('Failed to create company:', error.message); +} +``` + +## fetchUserPermissions + +The `fetchUserPermissions` function retrieves the current user's role permissions and returns both the flattened permission IDs and the raw role response. This function is used internally by other API functions to determine what data the user can access and modify. It uses the [`customer` query](https://developer.adobe.com/commerce/webapi/graphql/schema/customer/queries/customer/) to fetch the role and permissions. + +If the user's role is identified as "Company Administrator" (the role ID is `0` or the role name is "Company Administrator"), the function automatically grants all company permissions, even if the permissions tree is empty. + +```ts +export const fetchUserPermissions = async (): Promise<{ + allowedIds: Set; + roleResponse: any; +}> +``` + +### Returns + +Returns a promise that resolves to an object containing a set of permission IDs and the raw GraphQL response with role data. + +```ts +{ + allowedIds: Set; + roleResponse: any; +} +``` + +### Common permission IDs + +| Permission ID | Description | +|-------------------------------------------|--------------------------------| +| `Magento_Company::view_account` | View company account info | +| `Magento_Company::edit_account` | Edit company account fields | +| `Magento_Company::view_address` | View legal address | +| `Magento_Company::edit_address` | Edit legal address | +| `Magento_Company::contacts` | View company contacts | +| `Magento_Company::payment_information` | View payment methods | +| `Magento_Company::shipping_information` | View shipping methods | + +### Usage + +```js +import { fetchUserPermissions } from '@dropins/storefront-company-management/api'; + +try { + const { allowedIds, roleResponse } = await fetchUserPermissions(); + + console.log('User role:', roleResponse.data.customer.role.name); + + if (allowedIds.has('Magento_Company::edit_account')) { + console.log('User can edit company account fields'); + enableEditButton(); + } + + // Company Administrators automatically have all permissions + const isAdmin = roleResponse.data.customer.role.id === '0'; + if (isAdmin) { + console.log('User is a Company Administrator'); + } +} catch (error) { + console.error('Failed to fetch user permissions:', error.message); +} +``` + +## getCompany + +The `getCompany` function retrieves company information based on the current user's role permissions. It first fetches the role permission tree, then dynamically queries only the allowed company fields. + +```ts +export const getCompany = async (): Promise +``` + +### Returns + +Returns a promise that resolves to a `CompanyModel` object with company information and computed permission flags. + +```ts +type CompanyModel = { + id: string; + name: string; + email: string; + legalName?: string; + vatTaxId?: string; + resellerId?: string; + legalAddress?: { + street: string[]; + city: string; + region?: { region: string; regionCode: string; regionId: number }; + countryCode: string; + postcode: string; + telephone?: string; + }; + companyAdmin?: { + id: string; + firstname: string; + lastname: string; + email: string; + jobTitle?: string; + }; + salesRepresentative?: { + firstname: string; + lastname: string; + email: string; + }; + availablePaymentMethods?: { code: string; title: string }[]; + availableShippingMethods?: { code: string; title: string }[]; + canEditAccount: boolean; + canEditAddress: boolean; + permissionsFlags: { + canViewAccount: boolean; + canEditAccount: boolean; + canViewAddress: boolean; + canEditAddress: boolean; + canViewContacts: boolean; + canViewPaymentInformation: boolean; + canViewShippingInformation: boolean; + }; +}; +``` + +### Usage + +```js +import { getCompany } from '@dropins/storefront-company-management/api'; + +try { + const company = await getCompany(); + + if (!company) { + console.log('No company data available'); + return; + } + + console.log('Company Name:', company.name); + console.log('Company Email:', company.email); + + if (company.legalAddress) { + console.log('Address:', company.legalAddress.street.join(', ')); + } + + if (company.canEditAccount) { + showEditButton(); + } +} catch (error) { + console.error('Failed to fetch company:', error.message); +} +``` + +## getCountries + +The `getCountries` function uses the [`countries` query](https://developer.adobe.com/commerce/webapi/graphql/schema/store/queries/countries/) to retrieve the list of available countries and their regions for company address forms. The function uses `sessionStorage` with the key `_company_countries` to cache results. Cached data is returned on subsequent calls to avoid repeated API requests. + + +```ts +export const getCountries = async (): Promise<{ + availableCountries: Country[]; + countriesWithRequiredRegion: string[]; + optionalZipCountries: string[]; +}> +``` + +### Returns + +Returns a promise that resolves to a countries data object. + +```ts +type CountriesData = { + availableCountries: Array<{ + value: string; // ISO 2-letter country code + text: string; // Country display name + }>; + countriesWithRequiredRegion: string[]; // Countries requiring region selection + optionalZipCountries: string[]; // Countries where postal code is optional +}; +``` + +### Usage + +```js +import { getCountries } from '@dropins/storefront-company-management/api'; + +// Get all countries (cached after first call) +const countriesData = await getCountries(); +console.log('Available countries:', countriesData.availableCountries); +console.log('Countries requiring regions:', countriesData.countriesWithRequiredRegion); + +// Check if a country requires a region +const isRegionRequired = countriesData.countriesWithRequiredRegion.includes('US'); +``` + +## getCustomerCompany + +The `getCustomerCompany` function calls the [`customer` query](https://developer.adobe.com/commerce/webapi/graphql/schema/customer/queries/customer/) to retrieve basic company information for the current customer. This is a simplified API that returns essential company info without requiring full company permissions. It first checks if company features are enabled before fetching data. + +```ts +export const getCustomerCompany = async (): Promise +``` + +### Returns + +Returns a promise that resolves to customer company information or null if any of the following conditions are met: + +- Company features are not enabled +- The customer is not associated with a company +- An error occurs + +### Usage + +```js +import { getCustomerCompany } from '@dropins/storefront-company-management/api'; + +try { + const customerCompany = await getCustomerCompany(); + + if (customerCompany) { + console.log('Customer company:', customerCompany.name); + displayCompanyBadge(customerCompany); + } else { + console.log('Customer is not part of a company'); + hideCompanyFeatures(); + } +} catch (error) { + console.error('Failed to fetch customer company:', error.message); +} +``` + +## isCompanyAdmin + +The `isCompanyAdmin` function uses the [`customer` query](https://developer.adobe.com/commerce/webapi/graphql/schema/customer/queries/customer/) to check if the current authenticated user is a Company Administrator. Company Administrators have full access to all company features. + +```ts +export const isCompanyAdmin = async (): Promise +``` + +### Returns + +Returns a promise that resolves to `true` if the user is a Company Administrator, `false` otherwise. + +### Usage + +```js +import { isCompanyAdmin } from '@dropins/storefront-company-management/api'; + +const isAdmin = await isCompanyAdmin(); +if (isAdmin) { + console.log('User is a Company Administrator'); + showAdminFeatures(); +} else { + console.log('User has limited permissions'); +} +``` + +## isCompanyUser + +The `isCompanyUser` function checks if the current authenticated user is associated with a company. It calls the [`customer` query](https://developer.adobe.com/commerce/webapi/graphql/schema/customer/queries/customer/). + +```ts +export const isCompanyUser = async (): Promise +``` + +### Returns + +Returns a promise that resolves to `true` if the user is part of a company, `false` otherwise. + +### Usage + +```js +import { isCompanyUser } from '@dropins/storefront-company-management/api'; + +const hasCompany = await isCompanyUser(); +if (hasCompany) { + console.log('User is part of a company'); + renderCompanyFeatures(); +} else { + console.log('User is not part of a company'); + showJoinCompanyPrompt(); +} +``` + +## updateCompany + +The `updateCompany` function updates company information. It only sends fields the user is permitted to edit based on their role permissions. The function dynamically builds the response of the [`updateCompany` mutation](https://developer.adobe.com/commerce/webapi/graphql/schema/b2b/company/mutations/update/) response to request only allowed fields. + +The function includes logic for handling street addresses (arrays vs strings) and regions (predefined vs custom regions). Custom regions are sent with `region_id: 0`. + +```ts +export const updateCompany = async ( + companyData: Partial +): Promise +``` + +Parameter | Type | Req? | Description +--- | --- | --- | --- +`companyData` | `Partial` | Yes | Partial company data object containing fields to update. + + +### Returns + +Returns a promise that resolves to the updated `CompanyModel` object. + +### Usage + +```js +import { updateCompany } from '@dropins/storefront-company-management/api'; + +try { + const updatedCompany = await updateCompany({ + name: 'New Company Name', + email: 'newemail@company.com', + legalAddress: { + street: ['123 Business Ave', 'Suite 100'], + city: 'San Francisco', + region: { region: 'California', regionCode: 'CA' }, + countryCode: 'US', + postcode: '94105', + telephone: '+1-555-123-4567' + } + }); + + console.log('Company updated successfully:', updatedCompany.name); +} catch (error) { + console.error('Failed to update company:', error.message); +} +``` + +For countries without predefined regions: + +```js +// Update with custom region (e.g., for Uganda) +await updateCompany({ + legalAddress: { + street: ['456 International Blvd'], + city: 'Kampala', + region: { + region: 'Central Region', + regionCode: 'Central Region' // Same as region for custom + }, + countryCode: 'UG', + postcode: '12345' + } +}); +``` + +## validateCompanyEmail + +The `validateCompanyEmail` function uses the [`isCompanyEmailAvailable` query](https://developer.adobe.com/commerce/webapi/graphql/schema/b2b/company/queries/is-company-admin-email-available/) to validate if an email address is available for company registration or updates. It checks against existing company emails in the system. + +```ts +export const validateCompanyEmail = async ( + email: string +): Promise +``` + +Parameter | Type | Req? | Description +--- | --- | --- | --- +`email` | string |Yes | The email address to validate. + +### Returns + +Returns a promise that resolves to a validation result object: + +```ts +type ValidateCompanyEmailResponse = { + isValid: boolean; + error?: string; +} +``` + +### Usage + +```js +import { validateCompanyEmail } from '@dropins/storefront-company-management/api'; + +const result = await validateCompanyEmail('test@company.com'); + +if (result.isValid) { + console.log('Email is available'); + allowFormSubmission(); +} else { + console.log('Email validation failed:', result.error); + showErrorMessage(result.error); +} +``` + diff --git a/src/content/docs/dropins-b2b/company/index.mdx b/src/content/docs/dropins-b2b/company/index.mdx new file mode 100644 index 000000000..6b9d92fb3 --- /dev/null +++ b/src/content/docs/dropins-b2b/company/index.mdx @@ -0,0 +1,56 @@ +--- +title: Company overview +description: Learn about the features and functions of the Company drop-in component. +sidebar: + label: Overview + order: 1 +--- + +import { Badge } from '@astrojs/starlight/components'; +import OptionsTable from '@components/OptionsTable.astro'; + +The Company drop-in component provides comprehensive B2B company management functionality for Adobe Commerce storefronts. It enables B2B customers to view and manage their company profile information, including company details, legal address, contacts, and payment/shipping methods. The component is permissions-aware, ensuring users can only access and modify information based on their assigned company role. + +## Supported Commerce features + +The following table provides an overview of the Adobe Commerce features that the Company supports: + +| Feature | Status | +| ---------------------------------------------------------------- | ------------------------------------------ | +| Company profile management | | +| Role-based permissions | | +| Legal address management | | +| Company contact information | | +| Payment methods configuration | | +| Shipping methods configuration | | +| Multi-language support | | +| Custom regions for international addresses | | +| Email validation | | +| GraphQL API integration | | +| Company hierarchy management | | +| Advanced user role management | | + +## Company topics + +The topics in this section will help you understand how to customize and use the Company effectively within your storefront. + +### Initialization + +Understand how to initialize the Company drop-in with proper configuration options, language definitions, and data model extensions. This topic covers the initialization process, configuration parameters, and how to set up internationalization for multi-language support. Visit the [Company initialization](/dropins-b2b/company/initialization/) page to learn more. + +### Containers + +Explore the structural elements and containers available in the Company drop-in. This topic covers the CompanyProfile and CustomerCompanyInfo containers, their configuration options, and how to customize the user experience through various settings and slots. {/* Visit the [Company containers](/dropins-b2b/company/containers/) page to learn more. */} + +### Data events + +Learn about the data events emitted by the Company drop-in component. This topic covers event types, data payloads, event lifecycle, and how to listen for and respond to company-related events in your application. Visit the [Company data events](/dropins-b2b/company/events/) page to learn more. + +### Functions + +Discover the API functions provided by the Company drop-in for dynamic information retrieval and management. This topic covers all available functions including getCompany, updateCompany, getCountries, and permission management functions, along with their usage examples and return types. Visit the [Company functions](/dropins-b2b/company/functions/) page to learn more. + +### Dictionary + +Understand how to customize text and implement internationalization in the Company drop-in. This topic covers the dictionary structure, language definitions, text customization options, and how to add support for additional languages in your company management interface. Visit the [Company dictionary](/dropins-b2b/company/dictionary/) page to learn more. + diff --git a/src/content/docs/dropins-b2b/company/initialization.mdx b/src/content/docs/dropins-b2b/company/initialization.mdx new file mode 100644 index 000000000..6da3017ab --- /dev/null +++ b/src/content/docs/dropins-b2b/company/initialization.mdx @@ -0,0 +1,230 @@ +--- +title: Company initialization +description: Learn how to configure the initializer for the Company drop-in component. +sidebar: + label: Initialization + order: 3 +--- + +import OptionsTable from '@components/OptionsTable.astro'; + +The Company drop-in initializer provides configuration options for setting up language definitions, extending data models, and customizing the component behavior. It enables internationalization support and allows you to extend the default company data models with custom fields and transformations. + +## Configuration options + +The Company initializer accepts the following configuration options to customize behavior and extend functionality: + +>', 'No', 'Language definitions for internationalization support. Allows customization of all text displayed in the component.'], + ['models', 'Record', 'No', 'Extend default data models with custom fields and transformations for company-specific requirements.'], + ]} +/> + +### Basic initialization + +The simplest way to initialize the Company drop-in with default settings: + +```ts +import { initialize } from '@dropins/storefront-company-management/api'; + +// Basic initialization +await initialize(); +``` + +### Advanced initialization + +Initialize with custom configuration options: + +```ts +import { initialize } from '@dropins/storefront-company-management/api'; +import { initializers } from '@dropins/tools/initializer.js'; + +// Advanced initialization with configuration +const initializeCompany = async () => { + await initialize({ + langDefinitions: { + en_US: { + 'Company.CompanyProfile.containerTitle': 'Business Profile', + 'Company.shared.fields.companyName': 'Business Name' + } + }, + models: { + Company: { + transformer: (data) => ({ + ...data, + customField: data?.custom_field, + }), + }, + }, + }); +}; + +// Register the initializer +initializers.register(initializeCompany, {}); +``` + +### Set language definitions + +The `langDefinitions` option allows you to customize text throughout the Company drop-in component. You can override individual dictionary keys or provide complete translations for different languages. + +```ts +// Load custom language definitions +const customEnglish = { + 'Company.CompanyProfile.containerTitle': 'Business Information', + 'Company.shared.fields.companyName': 'Business Name', + 'Company.shared.fields.legalName': 'Legal Business Name', + 'Company.shared.buttons.edit': 'Modify', + 'Company.shared.buttons.save': 'Update Information' +}; + +// Load French translations +const frenchTranslations = await fetch('/i18n/company_fr_FR.json') + .then(res => res.json()); + +// Initialize with multiple language support +await initialize({ + langDefinitions: { + en_US: customEnglish, + fr_FR: frenchTranslations + } +}); +``` + +#### Multi-language setup + +For applications supporting multiple languages, you can load different language files dynamically: + +```ts +// Dynamic language loading based on user preference +const userLocale = getUserLocale(); // Your function to get user's locale +const languageDefinitions = {}; + +if (userLocale === 'fr_FR') { + languageDefinitions.fr_FR = await fetch('/i18n/company_fr_FR.json') + .then(res => res.json()); +} else if (userLocale === 'es_ES') { + languageDefinitions.es_ES = await fetch('/i18n/company_es_ES.json') + .then(res => res.json()); +} + +await initialize({ + langDefinitions: languageDefinitions +}); +``` + +### Set models + +The `models` option allows you to extend the default Company data models with custom fields and transformations. This is useful when you have custom company attributes or need to transform data from your backend. + +#### Default models + +The Company drop-in includes these default models: +- **Company**: Main company information model +- **CompanyAddress**: Legal address model +- **CompanyContact**: Contact information model + +#### Extending models with custom fields + +```ts +await initialize({ + models: { + Company: { + transformer: (data) => ({ + // Include all default fields + ...data, + // Add custom fields + industryType: data?.industry_type, + yearEstablished: data?.year_established, + employeeCount: data?.employee_count, + customAttributes: data?.custom_attributes?.reduce((acc, attr) => { + acc[attr.attribute_code] = attr.value; + return acc; + }, {}) + }), + }, + CompanyAddress: { + transformer: (data) => ({ + ...data, + // Add custom address fields + buildingName: data?.building_name, + floorNumber: data?.floor_number, + deliveryInstructions: data?.delivery_instructions + }), + } + } +}); +``` + +#### Complex model transformations + +For more complex data transformations, you can implement custom logic: + +```ts +await initialize({ + models: { + Company: { + transformer: (data) => { + // Transform payment methods to include additional metadata + const enhancedPaymentMethods = data?.available_payment_methods?.map(method => ({ + ...method, + isPreferred: method.code === data?.preferred_payment_method, + fees: getPaymentMethodFees(method.code), // Your custom function + })); + + // Calculate company metrics + const companyMetrics = { + totalOrders: data?.order_statistics?.total_orders || 0, + averageOrderValue: data?.order_statistics?.average_order_value || 0, + creditLimit: data?.credit_limit || 0, + creditUsed: data?.credit_used || 0, + creditAvailable: (data?.credit_limit || 0) - (data?.credit_used || 0) + }; + + return { + ...data, + availablePaymentMethods: enhancedPaymentMethods, + metrics: companyMetrics, + // Format display names + displayName: data?.legal_name || data?.name, + shortName: data?.name?.substring(0, 20) + (data?.name?.length > 20 ? '...' : '') + }; + }, + } + } +}); +``` + +#### Model validation + +You can also add validation logic to your model transformers: + +```ts +await initialize({ + models: { + Company: { + transformer: (data) => { + // Validate required fields + if (!data?.name) { + console.warn('Company name is missing'); + } + + if (!data?.email) { + console.warn('Company email is missing'); + } + + // Sanitize and validate data + return { + ...data, + name: data?.name?.trim(), + email: data?.email?.toLowerCase().trim(), + vatTaxId: data?.vat_tax_id?.replace(/[^A-Za-z0-9]/g, ''), // Remove special chars + phoneNumber: formatPhoneNumber(data?.legal_address?.telephone) // Your formatting function + }; + }, + } + } +}); +``` + diff --git a/src/content/docs/dropins-b2b/company/installation.mdx b/src/content/docs/dropins-b2b/company/installation.mdx new file mode 100644 index 000000000..77b2185e2 --- /dev/null +++ b/src/content/docs/dropins-b2b/company/installation.mdx @@ -0,0 +1,241 @@ +--- +title: Company installation +description: Learn how to install the Company drop-in component into your site. +sidebar: + label: Installation + order: 2 +--- + +import { Tabs, TabItem, Steps } from '@astrojs/starlight/components'; +import Tasks from '@components/Tasks.astro'; +import Task from '@components/Task.astro'; +import Callouts from '@components/Callouts.astro'; +import Diagram from '@components/Diagram.astro'; +import Vocabulary from '@components/Vocabulary.astro'; + +The Company drop-in component requires Adobe Commerce B2B features to be enabled and properly configured. It integrates with the Adobe Commerce GraphQL API to provide comprehensive company management functionality. The component is compatible with modern browsers and requires JavaScript ES6+ support for optimal performance. + +## Step-by-step + +Use the following steps to install the Company component: + +:::note +These steps are specific for Edge Delivery Services projects, and may not necessarily be the same for other frameworks such as a static HTML or React framework. +::: + + + + + +### Install the packages + +Install the Company drop-in component and its required dependencies. The `@dropins/tools` package provides essential utilities for GraphQL communication, event handling, and component initialization. + + ```bash frame="none" + npm install @dropins/tools @dropins/storefront-company-management + ``` + +:::note +All drop-in components require the `@dropins/tools` package. This package contains the libraries that drop-in components need to initialize and communicate, including `fetch-graphql`, `event-bus`, and `initializer` utilities. +::: + + + + +### Map the packages + +Configure the import map to enable proper module resolution for the Company drop-in component. Add the Company package mapping to your existing import map configuration. + +This example shows an `importmap` added to the `head.html` file. + +```json title="head.html" + +``` + +With the `importmap` defined, you can now **import the required files** from these packages into your Company block as described in the next step. + + + + +### Import the required files + +Import the necessary modules for the Company drop-in component. These imports provide GraphQL communication, initialization tools, API functions, rendering capabilities, and UI containers. + +```js title="Company.js" +// GraphQL Client +import * as mesh from '@dropins/tools/fetch-graphql.js'; + +// component tools +import { initializers } from '@dropins/tools/initializer.js'; + +// drop-in component functions +import * as companyApi from '@dropins/storefront-company-management/api.js'; + +// Drop-in component provider +import { render as provider } from '@dropins/storefront-company-management/render.js'; + +// Drop-in component containers +import { CompanyProfile, CustomerCompanyInfo } from '@dropins/storefront-company-management/containers.js'; +``` + + + +### GraphQL client + +Enables the ability to set and get endpoints and headers, as well as fetches data and configurations. + +### Drop-in component tools + +The Initializer is responsible for setting up event listeners and initializing a module with the given configuration. + +### drop-in component functions + +Provides API functions for company management including getCompany, updateCompany, getCountries, and permission handling. + +### Drop-in component provider + +Renders the Company UI components and manages their lifecycle and state. + +### Drop-in component containers + +Structural elements including CompanyProfile and CustomerCompanyInfo containers that manage and display company information. + + + + + + +### Connect to the endpoint + +Configure the GraphQL endpoint to connect the Company drop-in to your Adobe Commerce B2B instance. This enables the component to fetch and update company information. + +```js title="Company.js" +// Set endpoint configuration +mesh.setEndpoint('https:///graphql'); +``` + +#### Request headers + +Configure authentication and store headers required for B2B company operations. The customer token is essential for accessing company-specific data and permissions. + +```js title="Company.js" +// Set the customer token for authenticated requests +companyApi.setFetchGraphQlHeader('authorization', 'Bearer '); + +// Set store code header for multi-store environments +mesh.setFetchGraphQlHeader('store', ''); + +// Optional: Set additional headers for B2B features +mesh.setFetchGraphQlHeader('content-type', 'application/json'); +``` + +:::note +Note that this setting is specific for GraphQL Mesh, so refer to the [Commerce GraphQL Service](https://developer.adobe.com/commerce/webapi/graphql/usage/headers/#request-headers) for direct connections. Visit the [user authentication](/dropins/user-auth/) drop-in component for instructions on setting up Authentication features. +::: + + + + +### Register and load the drop-in + +Initialize the Company drop-in component with proper configuration including language definitions and any custom settings. This step registers the component and prepares it for rendering. + +```js title="Company.js" +// Initialize the Company drop-in +const initialize = async () => { + // Initialize the company API with configuration + await companyApi.initialize({ + langDefinitions: { + en_US: { + // Custom language overrides if needed + 'Company.CompanyProfile.containerTitle': 'My Company Profile' + } + } + }); +}; + +// Register the initializer +initializers.register(initialize, {}); +``` + +```html title="index.html" + +``` + + +1. The `initialize` function sets up the Company drop-in with configuration options and language definitions. +2. The `mountImmediately` function loads all registered drop-in components after the page has loaded. + + + + + +### Render the drop-in + +Render the Company containers on your page. You can use either the CompanyProfile container for full company management or the CustomerCompanyInfo container for basic company display. + +```js title="Company.js" +// Render CompanyProfile container (full company management) +provider.render(CompanyProfile, { + withHeader: true, + className: 'company-profile-section' +})(document.getElementById('company-profile')); + +// Render CustomerCompanyInfo container (basic company info display) +provider.render(CustomerCompanyInfo, { + withHeader: false, + className: 'customer-company-widget' +})(document.getElementById('customer-company-info')); +``` + +#### Verification steps + +To verify the installation is working correctly: + +1. **Check browser console** for any JavaScript errors +2. **Verify network requests** to the GraphQL endpoint are successful +3. **Test user permissions** by logging in with different company roles +4. **Validate data display** ensures company information loads correctly +5. **Test edit functionality** if the user has appropriate permissions + + + + + +## Summary + +The Company drop-in component installation follows the standard Adobe Commerce drop-in pattern: + +1. **Install packages** - Add the Company drop-in and tools packages to your project +2. **Configure import map** - Set up module resolution for proper component loading +3. **Import modules** - Import GraphQL client, initializers, API functions, and containers +4. **Connect endpoint** - Configure GraphQL endpoint and authentication headers +5. **Initialize component** - Register and load the drop-in with configuration options +6. **Render containers** - Display CompanyProfile or CustomerCompanyInfo on your pages + +The component integrates seamlessly with Adobe Commerce B2B features and provides role-based access control, ensuring users only see and can modify information appropriate to their permissions. Once installed, the component automatically handles data fetching, form validation, and state management for company information. +