Skip to content

Commit

Permalink
Merge pull request #46203 from VickyStash/feature/45176-invoicing-det…
Browse files Browse the repository at this point in the history
…ails-section

[No QA] Invoicing details section
  • Loading branch information
madmax330 authored Aug 13, 2024
2 parents 86da342 + b329c5c commit 9aa4eed
Show file tree
Hide file tree
Showing 18 changed files with 325 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2079,6 +2079,7 @@ const CONST = {
ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled',
ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled',
ARE_EXPENSIFY_CARDS_ENABLED: 'areExpensifyCardsEnabled',
ARE_INVOICES_ENABLED: 'areInvoicesEnabled',
ARE_TAXES_ENABLED: 'tax',
},
DEFAULT_CATEGORIES: [
Expand Down Expand Up @@ -2321,6 +2322,7 @@ const CONST = {
CARD_SECURITY_CODE: /^[0-9]{3,4}$/,
CARD_EXPIRATION_DATE: /^(0[1-9]|1[0-2])([^0-9])?([0-9]{4}|([0-9]{2}))$/,
ROOM_NAME: /^#[\p{Ll}0-9-]{1,100}$/u,
DOMAIN_BASE: '^(?:https?:\\/\\/)?(?:www\\.)?([^\\/]+)',

// eslint-disable-next-line max-len, no-misleading-character-class
EMOJI: /[\p{Extended_Pictographic}\u200d\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}\u20E3\uFE0F]|[#*0-9]\uFE0F?\u20E3/gu,
Expand Down
6 changes: 6 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,10 @@ const ONYXKEYS = {
WORKSPACE_TAX_NAME_FORM_DRAFT: 'workspaceTaxNameFormDraft',
WORKSPACE_TAX_VALUE_FORM: 'workspaceTaxValueForm',
WORKSPACE_TAX_VALUE_FORM_DRAFT: 'workspaceTaxValueFormDraft',
WORKSPACE_INVOICES_COMPANY_NAME_FORM: 'workspaceInvoicesCompanyNameForm',
WORKSPACE_INVOICES_COMPANY_NAME_FORM_DRAFT: 'workspaceInvoicesCompanyNameFormDraft',
WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM: 'workspaceInvoicesCompanyWebsiteForm',
WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM_DRAFT: 'workspaceInvoicesCompanyWebsiteFormDraft',
NEW_CHAT_NAME_FORM: 'newChatNameForm',
NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft',
SUBSCRIPTION_SIZE_FORM: 'subscriptionSizeForm',
Expand Down Expand Up @@ -676,6 +680,8 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm;
[ONYXKEYS.FORMS.WORKSPACE_TAX_CODE_FORM]: FormTypes.WorkspaceTaxCodeForm;
[ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm;
[ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_NAME_FORM]: FormTypes.WorkspaceInvoicesCompanyNameForm;
[ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM]: FormTypes.WorkspaceInvoicesCompanyWebsiteForm;
[ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm;
[ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm;
[ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm;
Expand Down
8 changes: 8 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,14 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/invoices',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/invoices` as const,
},
WORKSPACE_INVOICES_COMPANY_NAME: {
route: 'settings/workspaces/:policyID/invoices/company-name',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/invoices/company-name` as const,
},
WORKSPACE_INVOICES_COMPANY_WEBSITE: {
route: 'settings/workspaces/:policyID/invoices/company-website',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/invoices/company-website` as const,
},
WORKSPACE_TRAVEL: {
route: 'settings/workspaces/:policyID/travel',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/travel` as const,
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ const SCREENS = {
EXPENSIFY_CARD_SETTINGS_FREQUENCY: 'Workspace_ExpensifyCard_Settings_Frequency',
BILLS: 'Workspace_Bills',
INVOICES: 'Workspace_Invoices',
INVOICES_COMPANY_NAME: 'Workspace_Invoices_Company_Name',
INVOICES_COMPANY_WEBSITE: 'Workspace_Invoices_Company_Website',
TRAVEL: 'Workspace_Travel',
MEMBERS: 'Workspace_Members',
INVITE: 'Workspace_Invite',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3319,6 +3319,10 @@ export default {
viewUnpaidInvoices: 'View unpaid invoices',
sendInvoice: 'Send invoice',
sendFrom: 'Send from',
invoicingDetails: 'Invoicing details',
invoicingDetailsDescription: 'This info will appear on your invoices.',
companyName: 'Company name',
companyWebsite: 'Company website',
paymentMethods: {
personal: 'Personal',
business: 'Business',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3370,6 +3370,10 @@ export default {
viewUnpaidInvoices: 'Ver facturas emitidas pendientes',
sendInvoice: 'Enviar factura',
sendFrom: 'Enviar desde',
invoicingDetails: 'Detalles de facturación',
invoicingDetailsDescription: 'Esta información aparecerá en tus facturas.',
companyName: 'Nombre de la empresa',
companyWebsite: 'Sitio web de la empresa',
paymentMethods: {
personal: 'Personal',
business: 'Empresas',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.TAX_VALUE]: () => require<ReactComponentModule>('../../../../pages/workspace/taxes/ValuePage').default,
[SCREENS.WORKSPACE.TAX_CREATE]: () => require<ReactComponentModule>('../../../../pages/workspace/taxes/WorkspaceCreateTaxPage').default,
[SCREENS.WORKSPACE.TAX_CODE]: () => require<ReactComponentModule>('../../../../pages/workspace/taxes/WorkspaceTaxCodePage').default,
[SCREENS.WORKSPACE.INVOICES_COMPANY_NAME]: () => require<ReactComponentModule>('../../../../pages/workspace/invoices/WorkspaceInvoicingDetailsName').default,
[SCREENS.WORKSPACE.INVOICES_COMPANY_WEBSITE]: () => require<ReactComponentModule>('../../../../pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite').default,
[SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: () => require<ReactComponentModule>('../../../../pages/workspace/expensifyCard/issueNew/IssueNewCardPage').default,
[SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS]: () => require<ReactComponentModule>('../../../../pages/workspace/expensifyCard/WorkspaceCardSettingsPage').default,
[SCREENS.WORKSPACE.EXPENSIFY_CARD_SETTINGS_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/workspace/expensifyCard/WorkspaceSettlementAccountPage').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_VALUE,
SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE,
],
[SCREENS.WORKSPACE.INVOICES]: [SCREENS.WORKSPACE.INVOICES_COMPANY_NAME, SCREENS.WORKSPACE.INVOICES_COMPANY_WEBSITE],
[SCREENS.WORKSPACE.EXPENSIFY_CARD]: [
SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW,
SCREENS.WORKSPACE.EXPENSIFY_CARD_BANK_ACCOUNT,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,12 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.SHARE]: {
path: ROUTES.WORKSPACE_PROFILE_SHARE.route,
},
[SCREENS.WORKSPACE.INVOICES_COMPANY_NAME]: {
path: ROUTES.WORKSPACE_INVOICES_COMPANY_NAME.route,
},
[SCREENS.WORKSPACE.INVOICES_COMPANY_WEBSITE]: {
path: ROUTES.WORKSPACE_INVOICES_COMPANY_WEBSITE.route,
},
[SCREENS.WORKSPACE.EXPENSIFY_CARD_LIMIT]: {
path: ROUTES.WORKSPACE_EXPENSIFY_CARD_LIMIT.route,
},
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,12 @@ type SettingsNavigatorParamList = {
policyID: string;
taxID: string;
};
[SCREENS.WORKSPACE.INVOICES_COMPANY_NAME]: {
policyID: string;
};
[SCREENS.WORKSPACE.INVOICES_COMPANY_WEBSITE]: {
policyID: string;
};
[SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: {
policyID: string;
};
Expand Down
8 changes: 7 additions & 1 deletion src/libs/Url.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'react-native-url-polyfill/auto';
import CONST from '@src/CONST';
import type {Route} from '@src/ROUTES';

/**
Expand Down Expand Up @@ -70,4 +71,9 @@ function hasURL(text: string) {
return urlPattern.test(text);
}

export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL, addLeadingForwardSlash};
function extractUrlDomain(url: string): string | undefined {
const match = String(url).match(CONST.REGEX.DOMAIN_BASE);
return match?.[1];
}

export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL, addLeadingForwardSlash, extractUrlDomain};
10 changes: 2 additions & 8 deletions src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import playSound, {SOUNDS} from '@libs/Sound';
import * as Url from '@libs/Url';
import * as ValidationUtils from '@libs/ValidationUtils';
import Navigation from '@navigation/Navigation';
import * as IOU from '@userActions/IOU';
Expand Down Expand Up @@ -43,13 +44,6 @@ function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepC

const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(transaction?.amount ?? 0), transaction?.currency);

const extractUrlDomain = (url: string): string | undefined => {
const DOMAIN_BASE_REGEX = '^(?:https?:\\/\\/)?(?:www\\.)?([^\\/]+)';
const match = String(url).match(DOMAIN_BASE_REGEX);

return match?.[1];
};

const validate = useCallback(
(values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_COMPANY_INFO_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.MONEY_REQUEST_COMPANY_INFO_FORM> => {
const errors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.COMPANY_NAME, INPUT_IDS.COMPANY_WEBSITE]);
Expand All @@ -58,7 +52,7 @@ function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepC
if (!ValidationUtils.isValidWebsite(values.companyWebsite)) {
errors.companyWebsite = translate('bankAccount.error.website');
} else {
const domain = extractUrlDomain(values.companyWebsite);
const domain = Url.extractUrlDomain(values.companyWebsite);

if (!domain || !Str.isValidDomainName(domain)) {
errors.companyWebsite = translate('iou.invalidDomainError');
Expand Down
79 changes: 79 additions & 0 deletions src/pages/workspace/invoices/WorkspaceInvoicingDetailsName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import {useOnyx} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import TextInput from '@components/TextInput';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ValidationUtils from '@libs/ValidationUtils';
import Navigation from '@navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/WorkspaceInvoicesCompanyNameForm';

type WorkspaceInvoicingDetailsNameProps = StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.INVOICES_COMPANY_NAME>;

function WorkspaceInvoicingDetailsName({route}: WorkspaceInvoicingDetailsNameProps) {
const {policyID} = route.params;

const {translate} = useLocalize();
const {inputCallbackRef} = useAutoFocusInput();
const styles = useThemeStyles();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const submit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_NAME_FORM>) => {
// TODO: implement UpdateInvoiceCompanyName API call when it's supported
Navigation.goBack();
};

const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_NAME_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_NAME_FORM> =>
ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.COMPANY_NAME]);

return (
<AccessOrNotFoundWrapper
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
policyID={policyID}
// TODO: uncomment when CONST.POLICY.MORE_FEATURES.ARE_INVOICES_ENABLED is supported
// featureName={CONST.POLICY.MORE_FEATURES.ARE_INVOICES_ENABLED}
>
<ScreenWrapper
testID={WorkspaceInvoicingDetailsName.displayName}
shouldEnablePickerAvoiding={false}
shouldEnableMaxHeight
>
<HeaderWithBackButton title={translate('workspace.invoices.companyName')} />
<FormProvider
formID={ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_NAME_FORM}
submitButtonText={translate('common.save')}
onSubmit={submit}
style={[styles.flex1, styles.mh5]}
enabledWhenOffline
validate={validate}
>
<InputWrapper
InputComponent={TextInput}
inputID={INPUT_IDS.COMPANY_NAME}
label={translate('workspace.invoices.companyName')}
accessibilityLabel={translate('workspace.invoices.companyName')}
role={CONST.ROLE.PRESENTATION}
defaultValue={policy?.invoice?.companyName}
ref={inputCallbackRef}
/>
</FormProvider>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
);
}

WorkspaceInvoicingDetailsName.displayName = 'WorkspaceInvoicingDetailsName';

export default WorkspaceInvoicingDetailsName;
57 changes: 57 additions & 0 deletions src/pages/workspace/invoices/WorkspaceInvoicingDetailsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, {useMemo} from 'react';
import {useOnyx} from 'react-native-onyx';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import Section from '@components/Section';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

type WorkspaceInvoicingDetailsSectionProps = {
/** The current policy ID */
policyID: string;
};

function WorkspaceInvoicingDetailsSection({policyID}: WorkspaceInvoicingDetailsSectionProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);

const horizontalPadding = useMemo(() => (shouldUseNarrowLayout ? styles.ph5 : styles.ph8), [shouldUseNarrowLayout, styles]);

return (
<Section
title={translate('workspace.invoices.invoicingDetails')}
subtitle={translate('workspace.invoices.invoicingDetailsDescription')}
containerStyles={[styles.ph0, shouldUseNarrowLayout ? styles.pt5 : styles.pt8]}
subtitleStyles={horizontalPadding}
titleStyles={[styles.textStrong, horizontalPadding]}
childrenStyles={styles.pt5}
subtitleMuted
>
<MenuItemWithTopDescription
key={translate('workspace.invoices.companyName')}
shouldShowRightIcon
title={policy?.invoice?.companyName}
description={translate('workspace.invoices.companyName')}
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_INVOICES_COMPANY_NAME.getRoute(policyID))}
style={horizontalPadding}
/>
<MenuItemWithTopDescription
key={translate('workspace.invoices.companyWebsite')}
shouldShowRightIcon
title={policy?.invoice?.companyWebsite}
description={translate('workspace.invoices.companyWebsite')}
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_INVOICES_COMPANY_WEBSITE.getRoute(policyID))}
style={horizontalPadding}
/>
</Section>
);
}

WorkspaceInvoicingDetailsSection.displayName = 'WorkspaceInvoicingDetailsSection';

export default WorkspaceInvoicingDetailsSection;
Loading

0 comments on commit 9aa4eed

Please sign in to comment.