From 8a1655508ce8d7a3d24ded360acb5877ecb6ae69 Mon Sep 17 00:00:00 2001 From: Gustav Larsson Date: Thu, 16 Jan 2025 17:27:34 +0100 Subject: [PATCH 1/7] fix(Expense form): Update form inputs to use new inputs & improve error state --- components/FormField.tsx | 17 +- components/InputAmount.tsx | 289 ++++++++++++++++++ components/StyledInputAmount.tsx | 1 + .../form/AdditionalDetailsSection.tsx | 5 +- .../form/ExpenseItemsSection.tsx | 126 ++++---- .../form/FormSectionContainer.tsx | 15 +- .../submit-expense/form/InviteUserOption.tsx | 2 +- .../form/TypeOfExpenseSection.tsx | 11 +- components/ui/Input.tsx | 23 +- 9 files changed, 400 insertions(+), 89 deletions(-) create mode 100644 components/InputAmount.tsx diff --git a/components/FormField.tsx b/components/FormField.tsx index e4d28b89b52..2a0e5c6611b 100644 --- a/components/FormField.tsx +++ b/components/FormField.tsx @@ -7,6 +7,7 @@ import { useIntl } from 'react-intl'; import { isOCError } from '../lib/errors'; import { formatFormErrorMessage, RICH_ERROR_MESSAGES } from '../lib/form-utils'; +import PrivateInfoIcon from './icons/PrivateInfoIcon'; import { Input } from './ui/Input'; import { Label } from './ui/Label'; import { FormikZodContext, getInputAttributesFromZodSchema } from './FormikZod'; @@ -19,6 +20,7 @@ export function FormField({ placeholder, children, error: customError, + isPrivate, ...props }: { label?: string; @@ -26,7 +28,7 @@ export function FormField({ name: string; hint?: string; placeholder?: string; - children?: (props: { form: FormikProps; meta: any; field: any }) => JSX.Element; + children?: (props: { form: FormikProps; meta: any; field: any; hasError?: boolean }) => JSX.Element; required?: boolean; min?: number; max?: number; @@ -34,6 +36,7 @@ export function FormField({ disabled?: boolean; htmlFor?: string; error?: string; + isPrivate?: boolean; }) { const intl = useIntl(); const htmlFor = props.htmlFor || `input-${name}`; @@ -73,7 +76,17 @@ export function FormField({ } return (
- {label && } + {label && ( + + )} {hint &&

{hint}

} {children ? children({ form, meta, field: fieldAttributes }) : } {hasError && showError && ( diff --git a/components/InputAmount.tsx b/components/InputAmount.tsx new file mode 100644 index 00000000000..cde0dc38444 --- /dev/null +++ b/components/InputAmount.tsx @@ -0,0 +1,289 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getEmojiByCurrencyCode } from 'country-currency-emoji-flags'; +import { clamp, isNil, isUndefined, round } from 'lodash'; + +import { Currency, ZERO_DECIMAL_CURRENCIES } from '@/lib/constants/currency'; +import { floatAmountToCents, getCurrencySymbol, getDefaultCurrencyPrecision } from '@/lib/currency-utils'; +import { cn } from '@/lib/utils'; + +import { StyledCurrencyPicker } from '@/components/StyledCurrencyPicker'; +import StyledSpinner from '@/components/StyledSpinner'; +import { InputGroup } from '@/components/ui/Input'; +import { Separator } from '@/components/ui/Separator'; + +const formatCurrencyName = (currency, currencyDisplay) => { + if (currencyDisplay === 'SYMBOL') { + return getCurrencySymbol(currency); + } else if (currencyDisplay === 'CODE') { + return currency; + } else { + return `${getCurrencySymbol(currency)} ${currency}`; + } +}; + +const parseValueFromEvent = (e, precision, ignoreComma = false) => { + if (e.target.value === '') { + return null; + } else { + const parsedNumber = parseFloat(ignoreComma ? e.target.value.replace(',', '') : e.target.value); + return isNaN(parsedNumber) ? NaN : parsedNumber.toFixed(precision); + } +}; + +/** Formats value is valid, fallbacks on rawValue otherwise */ +const getValue = (value, rawValue, isEmpty) => { + if (isEmpty) { + return ''; + } + + return isNaN(value) || value === null ? rawValue : value / 100; +}; + +// const getError = (curVal, minAmount, required) => { +// return Boolean((required && isNil(curVal)) || (minAmount && curVal < minAmount)); +// }; + +/** Prevent changing the value when scrolling on the input */ +const ignoreOnWheel = e => { + e.preventDefault(); + e.target.blur(); +}; + +/** Returns the minimum width for an amount input, auto-adjusting to the number of digits */ +const useAmountInputMinWidth = (value, max = 1000000000) => { + const prevValue = React.useRef(value); + + // Do not change size if value becomes invalid (to prevent jumping) + if (typeof value?.toFixed !== 'function') { + return prevValue.current || '0.7em'; + } + + const maxLength = max.toString().length; + const valueLength = clamp(value.toFixed(2).length, 1, maxLength); + const result = `${valueLength * 0.7}em`; + prevValue.current = result; + return result; +}; + +const ConvertedAmountInput = ({ inputId, exchangeRate, onChange, baseAmount, minFxRate, maxFxRate }) => { + const precision = getDefaultCurrencyPrecision(exchangeRate.toCurrency); + const targetAmount = round((baseAmount || 0) * exchangeRate.value, precision); + const [isEditing, setEditing] = React.useState(false); + const [rawValue, setRawValue] = React.useState(targetAmount / 100 || ''); + const minWidth = useAmountInputMinWidth(targetAmount / 100); + const value = getValue(targetAmount, rawValue, false); + const isBaseAmountInvalid = isNaN(baseAmount) || isNil(baseAmount); + + const getLimitAmountFromFxRate = fxRate => { + return round(((baseAmount || 0) * fxRate) / 100.0, precision); + }; + + return ( +
+ = {exchangeRate.toCurrency} + 0.01, Precision=0 -> 1 + min={minFxRate ? getLimitAmountFromFxRate(minFxRate) : 1 / 10 ** precision} + max={maxFxRate ? getLimitAmountFromFxRate(maxFxRate) : undefined} + value={isBaseAmountInvalid ? '' : isEditing ? value : value.toFixed(precision)} + onWheel={ignoreOnWheel} + required + placeholder={!precision ? '--' : `--.${'-'.repeat(precision)}`} + disabled={isBaseAmountInvalid} + onChange={e => { + setEditing(true); + setRawValue(e.target.value); + const convertedAmount = parseFloat(e.target.value); + if (!convertedAmount) { + onChange({ ...exchangeRate, value: null }); + } else { + const newFxRate = round((convertedAmount * 100) / (baseAmount || 1), 8); + onChange({ + ...exchangeRate, + value: newFxRate, + source: 'USER', + isApproximate: false, + date: null, + }); + } + }} + onBlur={() => { + setEditing(false); + }} + /> + {getEmojiByCurrencyCode(exchangeRate.toCurrency)} +
+ ); +}; + +ConvertedAmountInput.propTypes = { + exchangeRate: PropTypes.object, + onChange: PropTypes.func, + baseAmount: PropTypes.number, + minFxRate: PropTypes.number, + maxFxRate: PropTypes.number, +}; + +/** Some custom styles to integrate the currency picker nicely */ +const CURRENCY_PICKER_STYLES = { + control: { + border: 'none', + background: '#F7F8FA', + minHeight: '37px', // 39px - 2px border + }, + menu: { + width: '280px', + }, + valueContainer: { + padding: '0 0 0 8px', + }, + input: { + margin: '0', + }, + dropdownIndicator: { + paddingLeft: '0', + paddingRight: '6px', + }, +}; + +/** + * An input for amount inputs. + */ +const InputAmount = ({ + currency, + currencyDisplay = 'SYMBOL', + name = 'amount', + min = 0, + max = 100000000000, + precision = ZERO_DECIMAL_CURRENCIES.includes(currency) ? 0 : 2, + defaultValue = undefined, + value, + onBlur = undefined, + onChange, + isEmpty = false, + hasCurrencyPicker = false, + onCurrencyChange = undefined, + availableCurrencies = Currency, + exchangeRate = undefined, + loadingExchangeRate = false, + onExchangeRateChange = undefined, + minFxRate = undefined, + maxFxRate = undefined, + className = null, + ...props +}) => { + const [rawValue, setRawValue] = React.useState(value || defaultValue || ''); + const isControlled = !isUndefined(value); + const curValue = isControlled ? getValue(value, rawValue, isEmpty) : undefined; + const minAmount = precision !== 0 ? min / 10 ** precision : min / 100; + const disabled = props.disabled || loadingExchangeRate; + const canUseExchangeRate = Boolean(!loadingExchangeRate && exchangeRate && exchangeRate.fromCurrency === currency); + const minWidth = useAmountInputMinWidth(curValue, max); + + const dispatchValue = (e, parsedValue) => { + if (isControlled) { + setRawValue(e.target.value); + } + if (onChange) { + const valueWithIgnoredComma = parseValueFromEvent(e, precision, true); + if (parsedValue === null || isNaN(parsedValue)) { + onChange(parsedValue, e); + } else if (!e.target.checkValidity() || parsedValue !== valueWithIgnoredComma) { + onChange(isNaN(e.target.value) ? NaN : null, e); + } else { + onChange(floatAmountToCents(parsedValue), e); + } + } + }; + + return ( + + {formatCurrencyName(currency, currencyDisplay)} +
+ ) : ( +
+ +
+ ) + } + prependClassName="px-0 py-0" + append={ + loadingExchangeRate ? ( +
+ +
+ ) : canUseExchangeRate ? ( +
+ + +
+ ) : undefined + } + appendClassName="px-0 py-0 bg-background" + width="100%" + type="number" + inputMode="decimal" + step={1 / 10 ** precision} + style={{ minWidth }} + name={name} + min={minAmount} + max={max / 100} + defaultValue={isUndefined(defaultValue) ? undefined : defaultValue / 100} + value={curValue} + onWheel={ignoreOnWheel} + onChange={e => { + e.stopPropagation(); + dispatchValue(e, parseValueFromEvent(e, precision)); + }} + onBlur={e => { + // Clean number if valid (ie. 41.1 -> 41.10) + const parsedNumber = parseValueFromEvent(e, precision); + const valueWithIgnoredComma = parseValueFromEvent(e, precision, true); + if ( + e.target.checkValidity() && + !(typeof parsedNumber === 'number' && isNaN(parsedNumber)) && + parsedNumber !== null && + valueWithIgnoredComma === parsedNumber + ) { + e.target.value = parsedNumber.toString(); + dispatchValue(e, parsedNumber); + } + + if (onBlur) { + onBlur(e); + } + }} + /> + ); +}; + +export default InputAmount; diff --git a/components/StyledInputAmount.tsx b/components/StyledInputAmount.tsx index 3cc70afc294..6042259b986 100644 --- a/components/StyledInputAmount.tsx +++ b/components/StyledInputAmount.tsx @@ -1,3 +1,4 @@ +// Deprecated, use components/InputAmount.tsx instead import React from 'react'; import PropTypes from 'prop-types'; import { getEmojiByCurrencyCode } from 'country-currency-emoji-flags'; diff --git a/components/submit-expense/form/AdditionalDetailsSection.tsx b/components/submit-expense/form/AdditionalDetailsSection.tsx index 3c657deed25..cd614f451bb 100644 --- a/components/submit-expense/form/AdditionalDetailsSection.tsx +++ b/components/submit-expense/form/AdditionalDetailsSection.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; +import { FormField } from '@/components/FormField'; + import { expenseTagsQuery } from '../../dashboard/filters/ExpenseTagsFilter'; import { AutocompleteEditTags } from '../../EditTags'; import ExpenseTypeTag from '../../expenses/ExpenseTypeTag'; import LoadingPlaceholder from '../../LoadingPlaceholder'; -import StyledInputFormikField from '../../StyledInputFormikField'; import { Label } from '../../ui/Label'; import { Step } from '../SubmitExpenseFlowSteps'; import { type ExpenseForm } from '../useExpenseForm'; @@ -26,7 +27,7 @@ export function AdditionalDetailsSection(props: AdditionalDetailsSectionProps) { return ( - +
{hasAttachment && (
- - {() => ( - { - setFieldValue(`expenseItems.${props.index}.attachment`, uploadResults[0].file); - }} - onSuccess={file => { - setFieldValue(`expenseItems.${props.index}.attachment`, file); - }} - onReject={msg => { - toast({ variant: 'error', message: msg }); - }} - /> - )} - + {() => { + return ( + { + setFieldValue(`expenseItems.${props.index}.attachment`, uploadResults[0].file); + }} + onSuccess={file => { + setFieldValue(`expenseItems.${props.index}.attachment`, file); + }} + onReject={msg => { + toast({ variant: 'error', message: msg }); + }} + /> + ); + }} +
)}
-
- ; }} - +
- - {() => ( - ( + setFieldValue(`expenseItems.${props.index}.amount.exchangeRate`, exchangeRate) } /> )} - +
{Boolean(item.amount?.currency && props.form.options.expenseCurrency !== item.amount?.currency) && ( @@ -411,22 +418,19 @@ function Taxes(props: { form: ExpenseForm }) { const intl = useIntl(); const { setFieldValue } = props.form; const onHasTaxChange = React.useCallback( - ({ checked }) => { - setFieldValue('hasTax', checked); + (checked: CheckedState) => { + setFieldValue('hasTax', Boolean(checked)); }, [setFieldValue], ); return (
- + {() => ( - + + +
)} - + -
+
{props.form.values.hasTax && props.form.options.taxType === TaxType.GST && ( - {({ field }) => ( @@ -466,26 +469,29 @@ function Taxes(props: { form: ExpenseForm }) { }))} /> )} - + )} {props.form.values.hasTax && props.form.options.taxType === TaxType.VAT && ( - - {({ field }) => ( - ( + - props.form.setFieldValue('tax.rate', e.target.value ? round(e.target.value / 100, 4) : null) + props.form.setFieldValue( + 'tax.rate', + isNumber(e.target.value) ? round(e.target.value / 100, 4) : null, + ) } minWidth={65} append="%" @@ -494,20 +500,18 @@ function Taxes(props: { form: ExpenseForm }) { step="0.01" /> )} - + )}
{props.form.values.hasTax && ( - -
+
{props.title || }
{props.subtitle || @@ -46,7 +38,6 @@ export function FormSectionContainer(props: FormSectionContainerProps) {
{props.children}
- {!!stepError &&
{stepError}
}
); } diff --git a/components/submit-expense/form/InviteUserOption.tsx b/components/submit-expense/form/InviteUserOption.tsx index da02e59ab59..aab0e7b5941 100644 --- a/components/submit-expense/form/InviteUserOption.tsx +++ b/components/submit-expense/form/InviteUserOption.tsx @@ -92,7 +92,7 @@ function NewOrganizationInviteeForm() { name="inviteeNewOrganization.organization.slug" > {({ field }) => ( - + )}
diff --git a/components/submit-expense/form/TypeOfExpenseSection.tsx b/components/submit-expense/form/TypeOfExpenseSection.tsx index 8d91d8ff757..6f9940e0070 100644 --- a/components/submit-expense/form/TypeOfExpenseSection.tsx +++ b/components/submit-expense/form/TypeOfExpenseSection.tsx @@ -5,8 +5,9 @@ import { CollectiveType } from '../../../lib/constants/collectives'; import { ExpenseType } from '../../../lib/graphql/types/v2/schema'; import { attachmentDropzoneParams } from '../../expenses/lib/attachments'; +import { FormField } from '@/components/FormField'; + import StyledDropzone from '../../StyledDropzone'; -import StyledInputFormikField from '../../StyledInputFormikField'; import { Label } from '../../ui/Label'; import { RadioGroup, RadioGroupCard } from '../../ui/RadioGroup'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../../ui/Tabs'; @@ -154,7 +155,7 @@ export function TypeOfExpenseSection(props: TypeOfExpenseSectionProps) {
- )} - +
- {} +interface InputProps extends React.InputHTMLAttributes { + hasError?: boolean; + error?: boolean; +} const Input = React.forwardRef(({ className, type, ...props }, ref) => { return ( @@ -10,6 +13,7 @@ const Input = React.forwardRef(({ className, type, type={type} className={cn( 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', + props.error && 'ring-2 ring-red-500 ring-offset-2', className, )} ref={ref} @@ -21,23 +25,30 @@ Input.displayName = 'Input'; const InputGroup = React.forwardRef< HTMLInputElement, - React.ComponentPropsWithoutRef<'input'> & { append?: React.ReactNode; prepend?: React.ReactNode } ->(({ className, prepend, append, ...props }, ref) => ( + React.ComponentPropsWithoutRef<'input'> & { + append?: React.ReactNode; + prepend?: React.ReactNode; + prependClassName?: string; + appendClassName?: string; + error?: boolean; + } +>(({ className, prepend, append, prependClassName, appendClassName, ...props }, ref) => (
- {prepend &&
{prepend}
} + {prepend &&
{prepend}
} - {append &&
{append}
} + {append &&
{append}
}
)); InputGroup.displayName = 'InputGroup'; From 53c851f511728004c9c44eeabdbd48c6969b99d0 Mon Sep 17 00:00:00 2001 From: Gustav Larsson Date: Tue, 21 Jan 2025 14:25:18 +0100 Subject: [PATCH 2/7] Refactor StyledDropzone to use tailwindCSS and renaming to Dropzone --- .../{StyledDropzone.tsx => Dropzone.tsx} | 189 ++++++------------ .../AddNewAttachedFilesButton.tsx | 6 +- .../attached-files/AttachedFilesForm.tsx | 4 +- components/attached-files/lib/attachments.ts | 2 +- components/collective-page/hero/HeroAvatar.js | 8 +- .../hero/HeroBackgroundCropperModal.js | 8 +- .../edit/EditFundraiser.tsx | 6 +- .../edit/EditProfile.tsx | 6 +- .../expenses/HostCreateExpenseModal.tsx | 5 +- .../legal-documents/UploadTaxFormModal.tsx | 6 +- .../transactions-imports/StepSelectCSV.tsx | 4 +- .../expenses/AddNewAttachedFilesButton.tsx | 6 +- .../expenses/ExpenseAttachedFilesForm.tsx | 4 +- components/expenses/ExpenseFormItems.js | 8 +- components/expenses/ExpenseItemForm.js | 7 +- components/expenses/lib/attachments.ts | 2 +- .../form/ExpenseItemsSection.tsx | 25 +-- .../form/TypeOfExpenseSection.tsx | 14 +- components/vendors/VendorForm.tsx | 2 +- 19 files changed, 120 insertions(+), 192 deletions(-) rename components/{StyledDropzone.tsx => Dropzone.tsx} (76%) diff --git a/components/StyledDropzone.tsx b/components/Dropzone.tsx similarity index 76% rename from components/StyledDropzone.tsx rename to components/Dropzone.tsx index a6b16487c6e..dc633c712c3 100644 --- a/components/StyledDropzone.tsx +++ b/components/Dropzone.tsx @@ -1,27 +1,22 @@ import React from 'react'; -import { ExclamationCircle } from '@styled-icons/fa-solid/ExclamationCircle'; -import { Download as DownloadIcon } from '@styled-icons/feather/Download'; -import { isNil, omit } from 'lodash'; -import { Upload } from 'lucide-react'; +import { isNil, isString, omit } from 'lodash'; +import { CircleAlert, Upload } from 'lucide-react'; import type { Accept, FileRejection } from 'react-dropzone'; import { useDropzone } from 'react-dropzone'; import { FormattedMessage, useIntl } from 'react-intl'; -import styled, { css } from 'styled-components'; import { v4 as uuid } from 'uuid'; import type { OcrParsingOptionsInput, UploadedFileKind, UploadFileResult } from '../lib/graphql/types/v2/schema'; import { useGraphQLFileUploader } from '../lib/hooks/useGraphQLFileUploader'; import { useImageUploader } from '../lib/hooks/useImageUploader'; +import { cn } from '@/lib/utils'; import { Button } from './ui/Button'; import { useToast } from './ui/useToast'; import type { ContainerProps } from './Container'; -import Container from './Container'; -import { Box } from './Grid'; import { getI18nLink } from './I18nFormatters'; import LocalFilePreview from './LocalFilePreview'; import StyledSpinner from './StyledSpinner'; -import { P, Span } from './Text'; import UploadedFilePreview from './UploadedFilePreview'; export const DROPZONE_ACCEPT_IMAGES = { 'image/*': ['.jpeg', '.png'] }; @@ -29,69 +24,10 @@ export const DROPZONE_ACCEPT_CSV = { 'text/csv': ['.csv'] }; export const DROPZONE_ACCEPT_PDF = { 'application/pdf': ['.pdf'] }; export const DROPZONE_ACCEPT_ALL = { ...DROPZONE_ACCEPT_IMAGES, ...DROPZONE_ACCEPT_PDF }; -const Dropzone = styled(Container)<{ onClick?: () => void; error?: any }>` - border: 1px dashed #c4c7cc; - border-radius: 10px; - text-align: center; - background: white; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - - ${props => - props.onClick && - css` - cursor: pointer; - - &:hover:not(:disabled) { - background: #f9f9f9; - border-color: ${props => props.theme.colors.primary[300]}; - } - - &:focus { - outline: 0; - border-color: ${props => props.theme.colors.primary[500]}; - } - `} - - ${props => - props.error && - css` - border: 1px solid ${props.theme.colors.red[500]}; - `} - - img { - max-height: 100%; - max-width: 100%; - } -`; - -const ReplaceContainer = styled.div` - box-sizing: border-box; - background: rgba(49, 50, 51, 0.5); - color: #ffffff; - cursor: pointer; - position: absolute; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 24px; - padding: 8px; - margin-top: -24px; - font-size: 12px; - line-height: 1em; - - &:hover { - background: rgba(49, 50, 51, 0.6); - } -`; - /** * A dropzone to upload one or multiple files */ -const StyledDropzone = ({ +const Dropzone = ({ onReject = undefined, onDrop = undefined, children = null, @@ -99,7 +35,6 @@ const StyledDropzone = ({ loadingProgress = undefined, minHeight = 96, size, - fontSize = '14px', mockImageGenerator = () => `https://loremflickr.com/120/120?lock=${uuid()}`, accept, minSize, @@ -122,6 +57,7 @@ const StyledDropzone = ({ limit = undefined, kind = null, showReplaceAction = true, + className = '', ...props }: StyledDropzoneProps) => { const { toast } = useToast(); @@ -181,69 +117,65 @@ const StyledDropzone = ({ minHeight = size || minHeight; const innerMinHeight = minHeight - 2; // -2 To account for the borders const dropProps = getRootProps(); + + const errorMsg = isString(error) ? error : undefined; return ( - img]:max-h-full [&>img]:max-w-full', + isDragActive && 'border-muted-foreground/50', + props.disabled && 'pointer-events-none opacity-60', + error && 'ring-2 ring-destructive ring-offset-2', + className, + )} {...props} {...(value ? omit(dropProps, ['onClick']) : dropProps)} - minHeight={size || minHeight} - size={size} - error={error} + style={{ height: size, width: size, minHeight: size || minHeight }} > {isLoading || isUploading || isUploadingWithGraphQL ? ( - - - {UploadingComponent ? : } - - {isUploading && {uploadProgress}%} - {isLoading && !isNil(loadingProgress) && {loadingProgress}%} - + {UploadingComponent ? : } +
+ {isUploading &&
{uploadProgress}%
} + {isLoading && !isNil(loadingProgress) &&
{loadingProgress}%
} +
) : ( - +
{isDragActive ? ( - - - - - - +
+ +

+ +

+
) : ( {!value ? ( - - {error ? ( - - -
- - {error} - -
-
+
+ {errorMsg ? ( +
+ +

{errorMsg}

+
) : isMulti ? (
{showIcon && ( -
+
)} @@ -255,7 +187,7 @@ const StyledDropzone = ({ />
{showInstructions && ( -

+

)} -

+

)}
) : (
{showIcon && ( -
+
)} @@ -303,7 +235,7 @@ const StyledDropzone = ({ )}
{showInstructions && ( -

+

-

+

)}
)} - +
) : typeof value === 'string' ? ( {showReplaceAction && ( - - +
)} ) : value instanceof File ? ( @@ -343,13 +276,13 @@ const StyledDropzone = ({ {children} )} -
+
)} {value && showActions && (
)} - +
); }; @@ -387,8 +320,6 @@ type StyledDropzoneProps = Omit + {({ getRootProps, getInputProps }) => (
@@ -28,7 +28,7 @@ const AddNewAttachedFilesButton = ({ disabled, mockImageGenerator, onSuccess, is
)} - +
); }; diff --git a/components/attached-files/AttachedFilesForm.tsx b/components/attached-files/AttachedFilesForm.tsx index f8ec20524db..bf275e93e50 100644 --- a/components/attached-files/AttachedFilesForm.tsx +++ b/components/attached-files/AttachedFilesForm.tsx @@ -5,9 +5,9 @@ import { FormattedMessage } from 'react-intl'; import type { UploadedFileKind } from '../../lib/graphql/types/v2/graphql'; import { attachmentDropzoneParams } from './lib/attachments'; +import Dropzone from '../Dropzone'; import { Flex } from '../Grid'; import PrivateInfoIcon from '../icons/PrivateInfoIcon'; -import StyledDropzone from '../StyledDropzone'; import StyledHr from '../StyledHr'; import { P, Span } from '../Text'; @@ -91,7 +91,7 @@ const AttachedFilesForm = ({ }} /> ) : ( - ( ); -const Dropzone = dynamic(() => import(/* webpackChunkName: 'react-dropzone' */ 'react-dropzone'), { +const ReactDropzone = dynamic(() => import(/* webpackChunkName: 'react-dropzone' */ 'react-dropzone'), { loading: DropzoneLoadingPlaceholder, ssr: false, }); @@ -146,7 +146,7 @@ const HeroAvatar = ({ collective, isAdmin, intl }) => { const imgType = isIndividualAccount(collective) ? 'AVATAR' : 'LOGO'; return ( - {
)} - + {showModal && ( { - + {({ isDragActive, isDragAccept, getRootProps, getInputProps, open }) => { const rootProps = getRootProps(); return ( @@ -286,7 +286,7 @@ const HeroBackgroundCropperModal = ({ onClose, collective }) => { ); }} - + ); }; diff --git a/components/crowdfunding-redesign/edit/EditFundraiser.tsx b/components/crowdfunding-redesign/edit/EditFundraiser.tsx index 264c6a62481..22cac9f8fd2 100644 --- a/components/crowdfunding-redesign/edit/EditFundraiser.tsx +++ b/components/crowdfunding-redesign/edit/EditFundraiser.tsx @@ -8,9 +8,9 @@ import { i18nGraphqlException } from '../../../lib/errors'; import { API_V2_CONTEXT } from '../../../lib/graphql/helpers'; import { isValidUrl } from '../../../lib/utils'; +import Dropzone, { DROPZONE_ACCEPT_IMAGES } from '../../Dropzone'; import { FormField } from '../../FormField'; import { FormikZod } from '../../FormikZod'; -import StyledDropzone, { DROPZONE_ACCEPT_IMAGES } from '../../StyledDropzone'; import Tabs from '../../Tabs'; import { Button } from '../../ui/Button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../../ui/Dialog'; @@ -58,7 +58,7 @@ const CoverImageForm = ({ schema, initialValues, onSubmit }) => { const hasValidUrl = field.value && isValidUrl(field.value); return ( - { maxSize={10e6} // in bytes, =10MB isMulti={false} showActions - size={196} + className="size-48" onSuccess={data => { if (data) { formik.setFieldValue(field.name, data.url); diff --git a/components/crowdfunding-redesign/edit/EditProfile.tsx b/components/crowdfunding-redesign/edit/EditProfile.tsx index 22cf9077d79..363c4ed4fa8 100644 --- a/components/crowdfunding-redesign/edit/EditProfile.tsx +++ b/components/crowdfunding-redesign/edit/EditProfile.tsx @@ -8,9 +8,9 @@ import { i18nGraphqlException } from '../../../lib/errors'; import { API_V2_CONTEXT } from '../../../lib/graphql/helpers'; import { isValidUrl } from '../../../lib/utils'; +import Dropzone, { DROPZONE_ACCEPT_IMAGES } from '../../Dropzone'; import { FormField } from '../../FormField'; import { FormikZod } from '../../FormikZod'; -import StyledDropzone, { DROPZONE_ACCEPT_IMAGES } from '../../StyledDropzone'; import { Button } from '../../ui/Button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../../ui/Dialog'; import { Separator } from '../../ui/Separator'; @@ -39,7 +39,7 @@ const CoverImageForm = ({ schema, initialValues, onSubmit }) => { {({ field }) => { const hasValidUrl = field.value && isValidUrl(field.value); return ( - { maxSize={10e6} // in bytes, =10MB isMulti={false} showActions - size={196} + className="size-48" onSuccess={data => { if (data) { formik.setFieldValue(field.name, data.url); diff --git a/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx b/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx index 4f1b8735eab..504a3b21ee5 100644 --- a/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx +++ b/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx @@ -27,10 +27,10 @@ import AccountingCategorySelect from '@/components/AccountingCategorySelect'; import { DefaultCollectiveLabel } from '../../../CollectivePicker'; import CollectivePickerAsync from '../../../CollectivePickerAsync'; +import Dropzone from '../../../Dropzone'; import { ExchangeRate } from '../../../ExchangeRate'; import { FormikZod } from '../../../FormikZod'; import type { BaseModalProps } from '../../../ModalContext'; -import StyledDropzone from '../../../StyledDropzone'; import { StyledInputAmountWithDynamicFxRate } from '../../../StyledInputAmountWithDynamicFxRate'; import StyledInputFormikField from '../../../StyledInputFormikField'; import StyledSelect from '../../../StyledSelect'; @@ -404,7 +404,7 @@ export const HostCreateExpenseModal = ({ } > {({ form, field, meta }) => ( - `https://loremflickr.com/120/120/invoice?lock=0`} - fontSize="13px" value={field.value && isValidUrl(field.value?.url) && field.value.url} useGraphQL={true} parseDocument={false} diff --git a/components/dashboard/sections/legal-documents/UploadTaxFormModal.tsx b/components/dashboard/sections/legal-documents/UploadTaxFormModal.tsx index 8ece5ddb832..d5170c6fd4e 100644 --- a/components/dashboard/sections/legal-documents/UploadTaxFormModal.tsx +++ b/components/dashboard/sections/legal-documents/UploadTaxFormModal.tsx @@ -7,8 +7,8 @@ import { API_V2_CONTEXT } from '../../../../lib/graphql/helpers'; import type { Account, Host, LegalDocument } from '../../../../lib/graphql/types/v2/schema'; import { getMessageForRejectedDropzoneFiles } from '../../../../lib/hooks/useImageUploader'; +import Dropzone, { DROPZONE_ACCEPT_PDF } from '../../../Dropzone'; import type { BaseModalProps } from '../../../ModalContext'; -import StyledDropzone, { DROPZONE_ACCEPT_PDF } from '../../../StyledDropzone'; import { Button } from '../../../ui/Button'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../../../ui/Dialog'; import { useToast } from '../../../ui/useToast'; @@ -87,7 +87,7 @@ export const UploadTaxFormModal = ({ }} />

- { if (rejected.length) { toast({ diff --git a/components/dashboard/sections/transactions-imports/StepSelectCSV.tsx b/components/dashboard/sections/transactions-imports/StepSelectCSV.tsx index 0fbe75ffe1a..3a35daf650a 100644 --- a/components/dashboard/sections/transactions-imports/StepSelectCSV.tsx +++ b/components/dashboard/sections/transactions-imports/StepSelectCSV.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import StyledDropzone, { DROPZONE_ACCEPT_CSV } from '../../../StyledDropzone'; +import Dropzone, { DROPZONE_ACCEPT_CSV } from '../../../Dropzone'; import { useStepper } from '../../../ui/Stepper'; export const StepSelectCSV = ({ onFileSelected }) => { const { nextStep } = useStepper(); return ( - + {({ getRootProps, getInputProps }) => (
@@ -69,7 +69,7 @@ const AddNewAttachedFilesButton = ({
)} - +
); }; diff --git a/components/expenses/ExpenseAttachedFilesForm.tsx b/components/expenses/ExpenseAttachedFilesForm.tsx index 3d58c70be85..8d7708a670e 100644 --- a/components/expenses/ExpenseAttachedFilesForm.tsx +++ b/components/expenses/ExpenseAttachedFilesForm.tsx @@ -5,9 +5,9 @@ import { FormattedMessage } from 'react-intl'; import type { Account } from '../../lib/graphql/types/v2/schema'; import { attachmentDropzoneParams } from './lib/attachments'; +import Dropzone from '../Dropzone'; import { Flex } from '../Grid'; import PrivateInfoIcon from '../icons/PrivateInfoIcon'; -import StyledDropzone from '../StyledDropzone'; import StyledHr from '../StyledHr'; import { P, Span } from '../Text'; @@ -67,7 +67,7 @@ const ExpenseAttachedFilesForm = ({ }} /> ) : ( - - `https://loremflickr.com/120/120/invoice?lock=${index}`} - mb={3} + className="mb-4" useGraphQL={hasOCRFeature} parseDocument={hasOCRFeature} parsingOptions={{ currency: values.currency }} @@ -208,7 +208,7 @@ class ExpenseFormItems extends React.PureComponent { values={{ 'i18n-bold': I18nBold }} />

-
+ ); } diff --git a/components/expenses/ExpenseItemForm.js b/components/expenses/ExpenseItemForm.js index 5612526fe9f..ebd0e4dc1c2 100644 --- a/components/expenses/ExpenseItemForm.js +++ b/components/expenses/ExpenseItemForm.js @@ -22,12 +22,12 @@ import { FX_RATE_ERROR_THRESHOLD, getExpenseExchangeRateWarningOrError } from '. import * as ScanningAnimationJSON from '../../public/static/animations/scanning.json'; import Container from '../Container'; +import Dropzone from '../Dropzone'; import { ExchangeRate } from '../ExchangeRate'; import { Box, Flex } from '../Grid'; import PrivateInfoIcon from '../icons/PrivateInfoIcon'; import RichTextEditor from '../RichTextEditor'; import StyledButton from '../StyledButton'; -import StyledDropzone from '../StyledDropzone'; import StyledHr from '../StyledHr'; import StyledInput from '../StyledInput'; import StyledInputAmount from '../StyledInputAmount'; @@ -337,7 +337,7 @@ const ExpenseItemForm = ({ required={!isOptional} error={meta.error?.type !== ERROR.FORM_FIELD_REQUIRED && formatFormErrorMessage(intl, meta.error)} > - `https://loremflickr.com/120/120/invoice?lock=${attachmentKey}`} - fontSize="13px" - size={[84, 112]} + className="size-20 sm:size-28" value={hasValidUrl && field.value} onReject={(...args) => { formRef.current.setFieldValue(itemPath, { ...attachment, __isUploading: false }); diff --git a/components/expenses/lib/attachments.ts b/components/expenses/lib/attachments.ts index c1ef101b9d5..3bf78dbad2e 100644 --- a/components/expenses/lib/attachments.ts +++ b/components/expenses/lib/attachments.ts @@ -1,6 +1,6 @@ import { ExpenseType } from '../../../lib/graphql/types/v2/schema'; -import { DROPZONE_ACCEPT_ALL } from '../../StyledDropzone'; +import { DROPZONE_ACCEPT_ALL } from '../../Dropzone'; export const attachmentDropzoneParams = { accept: DROPZONE_ACCEPT_ALL, diff --git a/components/submit-expense/form/ExpenseItemsSection.tsx b/components/submit-expense/form/ExpenseItemsSection.tsx index 0f0f98f96b4..4f6056536df 100644 --- a/components/submit-expense/form/ExpenseItemsSection.tsx +++ b/components/submit-expense/form/ExpenseItemsSection.tsx @@ -19,16 +19,13 @@ import { } from '../../expenses/lib/utils'; import { FormField } from '@/components/FormField'; +import InputAmount from '@/components/InputAmount'; import { Checkbox } from '@/components/ui/Checkbox'; +import Dropzone from '../../Dropzone'; import { ExchangeRate } from '../../ExchangeRate'; import FormattedMoneyAmount from '../../FormattedMoneyAmount'; import LoadingPlaceholder from '../../LoadingPlaceholder'; -import StyledCheckbox from '../../StyledCheckbox'; -import StyledDropzone from '../../StyledDropzone'; -import StyledInputAmount from '../../StyledInputAmount'; -import StyledInputFormikField from '../../StyledInputFormikField'; -import StyledInputGroup from '../../StyledInputGroup'; import StyledSelect from '../../StyledSelect'; import { Button } from '../../ui/Button'; import { Input, InputGroup } from '../../ui/Input'; @@ -38,7 +35,6 @@ import { Step } from '../SubmitExpenseFlowSteps'; import { type ExpenseForm } from '../useExpenseForm'; import { FormSectionContainer } from './FormSectionContainer'; -import InputAmount from '@/components/InputAmount'; type ExpenseItemsSectionProps = { form: ExpenseForm; @@ -215,17 +211,18 @@ function ExpenseItem(props: ExpenseItemProps) { label={intl.formatMessage({ defaultMessage: 'Upload file', id: '6oOCCL' })} name={`expenseItems.${props.index}.attachment`} > - {() => { + {({ field }) => { return ( - { @@ -312,7 +309,7 @@ function ExpenseItem(props: ExpenseItemProps) {
{Boolean(item.amount?.currency && props.form.options.expenseCurrency !== item.amount?.currency) && (
- (
- - {() => ( - ( + Date: Wed, 22 Jan 2025 14:24:32 +0100 Subject: [PATCH 3/7] Update AccountingCategorySelect to use ui/Button --- components/AccountingCategorySelect.tsx | 14 +++++++------- components/Dropzone.tsx | 4 ++-- .../sections/collectives/AddFundsModal.tsx | 1 - .../contributions/CreatePendingOrderModal.tsx | 1 - .../submit-expense/form/ExpenseCategorySection.tsx | 10 ++++++---- .../submit-expense/form/TypeOfExpenseSection.tsx | 3 ++- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/components/AccountingCategorySelect.tsx b/components/AccountingCategorySelect.tsx index 128027f27e5..360245f4b9c 100644 --- a/components/AccountingCategorySelect.tsx +++ b/components/AccountingCategorySelect.tsx @@ -13,6 +13,7 @@ import { cn } from '../lib/utils'; import { ACCOUNTING_CATEGORY_HOST_FIELDS } from './expenses/lib/accounting-categories'; import { isSameAccount } from '@/lib/collective'; +import { Button } from './ui/Button'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from './ui/Command'; import { Popover, PopoverContent, PopoverTrigger } from './ui/Popover'; @@ -378,16 +379,15 @@ const AccountingCategorySelect = ({ {children || ( - + )} diff --git a/components/Dropzone.tsx b/components/Dropzone.tsx index dc633c712c3..d41dcdf3d1e 100644 --- a/components/Dropzone.tsx +++ b/components/Dropzone.tsx @@ -59,7 +59,7 @@ const Dropzone = ({ showReplaceAction = true, className = '', ...props -}: StyledDropzoneProps) => { +}: DropzoneProps) => { const { toast } = useToast(); const intl = useIntl(); const imgUploaderParams = { isMulti, mockImageGenerator, onSuccess, onReject, kind, accept, minSize, maxSize }; @@ -307,7 +307,7 @@ type UploadedFile = { type: string; }; -type StyledDropzoneProps = Omit & { +type DropzoneProps = React.HTMLAttributes & { /** Called back with the rejected files */ onReject?: (msg: string) => void; /** Called when the user drops files */ diff --git a/components/dashboard/sections/collectives/AddFundsModal.tsx b/components/dashboard/sections/collectives/AddFundsModal.tsx index 5e407e69c6e..a5e1117963f 100644 --- a/components/dashboard/sections/collectives/AddFundsModal.tsx +++ b/components/dashboard/sections/collectives/AddFundsModal.tsx @@ -733,7 +733,6 @@ const AddFundsModalContentWithCollective = ({ account={account} selectedCategory={field.value} allowNone={true} - buttonClassName="rounded" /> )} diff --git a/components/dashboard/sections/contributions/CreatePendingOrderModal.tsx b/components/dashboard/sections/contributions/CreatePendingOrderModal.tsx index 6e5ab2626ae..551fc1c166f 100644 --- a/components/dashboard/sections/contributions/CreatePendingOrderModal.tsx +++ b/components/dashboard/sections/contributions/CreatePendingOrderModal.tsx @@ -557,7 +557,6 @@ const CreatePendingContributionForm = ({ host, onClose, error, edit }: CreatePen account={collective} selectedCategory={field.value} allowNone={true} - buttonClassName="rounded" /> )} diff --git a/components/submit-expense/form/ExpenseCategorySection.tsx b/components/submit-expense/form/ExpenseCategorySection.tsx index 42c5cdbc25f..8d45aa4f3b3 100644 --- a/components/submit-expense/form/ExpenseCategorySection.tsx +++ b/components/submit-expense/form/ExpenseCategorySection.tsx @@ -13,6 +13,7 @@ import { Step } from '../SubmitExpenseFlowSteps'; import type { ExpenseForm } from '../useExpenseForm'; import { FormSectionContainer } from './FormSectionContainer'; +import { FormField } from '@/components/FormField'; type ExpenseCategorySectionProps = { form: ExpenseForm; @@ -44,9 +45,10 @@ export function ExpenseCategorySection(props: ExpenseCategorySectionProps) { step={Step.EXPENSE_CATEGORY} inViewChange={props.inViewChange} > - - {() => ( + + {({ field }) => ( setFieldValue('accountingCategoryId', value?.id)} @@ -56,10 +58,10 @@ export function ExpenseCategorySection(props: ExpenseCategorySectionProps) { account={props.form.options.account} selectedCategory={selectedAccountingCategory} allowNone={!props.form.options.isAccountingCategoryRequired} - buttonClassName="rounded max-w-full w-full" + buttonClassName="max-w-full w-full" /> )} - + {instructions && ( diff --git a/components/submit-expense/form/TypeOfExpenseSection.tsx b/components/submit-expense/form/TypeOfExpenseSection.tsx index 315a2cc4a47..0e5d1277513 100644 --- a/components/submit-expense/form/TypeOfExpenseSection.tsx +++ b/components/submit-expense/form/TypeOfExpenseSection.tsx @@ -142,6 +142,7 @@ export function TypeOfExpenseSection(props: TypeOfExpenseSectionProps) { props.form.setFieldValue('hasInvoiceOption', newValue as YesNoOption)} + className="space-y-4" > @@ -171,7 +172,7 @@ export function TypeOfExpenseSection(props: TypeOfExpenseSectionProps) { name="invoice" className="min-h-16" // width={1} - // minHeight={64} + minHeight={64} // height={1} showActions useGraphQL={true} From 77f3eb3475e2a965273ccd5bb798bd13de4d7e46 Mon Sep 17 00:00:00 2001 From: Gustav Larsson Date: Wed, 22 Jan 2025 15:11:21 +0100 Subject: [PATCH 4/7] Add focus-visible state to RadioGroupCard --- components/ui/RadioGroup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/RadioGroup.tsx b/components/ui/RadioGroup.tsx index 5876482b3e5..9aaa43242da 100644 --- a/components/ui/RadioGroup.tsx +++ b/components/ui/RadioGroup.tsx @@ -47,7 +47,7 @@ const RadioGroupCard = React.forwardRef< >(({ className, children, showSubcontent, subContent, ...props }, ref) => { return (
Date: Wed, 22 Jan 2025 15:48:40 +0100 Subject: [PATCH 5/7] Scroll to first step with error --- components/Dropzone.tsx | 1 - components/submit-expense/SubmitExpenseFlow.tsx | 2 +- components/submit-expense/SubmitExpenseFlowSteps.tsx | 9 +++++++++ .../submit-expense/form/ExpenseCategorySection.tsx | 3 +-- components/submit-expense/form/FormSectionContainer.tsx | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/components/Dropzone.tsx b/components/Dropzone.tsx index d41dcdf3d1e..dc831397885 100644 --- a/components/Dropzone.tsx +++ b/components/Dropzone.tsx @@ -13,7 +13,6 @@ import { cn } from '@/lib/utils'; import { Button } from './ui/Button'; import { useToast } from './ui/useToast'; -import type { ContainerProps } from './Container'; import { getI18nLink } from './I18nFormatters'; import LocalFilePreview from './LocalFilePreview'; import StyledSpinner from './StyledSpinner'; diff --git a/components/submit-expense/SubmitExpenseFlow.tsx b/components/submit-expense/SubmitExpenseFlow.tsx index b456f22e1b9..372000d8429 100644 --- a/components/submit-expense/SubmitExpenseFlow.tsx +++ b/components/submit-expense/SubmitExpenseFlow.tsx @@ -407,7 +407,7 @@ export function SubmitExpenseFlow(props: SubmitExpenseFlowProps) {
-
+
!isExpenseFormStepCompleted(form, s)); + // Scroll to first step with error + const firstIncompleteSection = stepOrder.find(s => !isExpenseFormStepCompleted(form, s)); + const submitCount = form.submitCount; + React.useEffect(() => { + if (firstIncompleteSection && submitCount > 0) { + document.querySelector(`#${firstIncompleteSection}`)?.scrollIntoView({ behavior: 'smooth' }); + } + }, [hasErrors, firstIncompleteSection, submitCount]); + return (
    diff --git a/components/submit-expense/form/ExpenseCategorySection.tsx b/components/submit-expense/form/ExpenseCategorySection.tsx index 8d45aa4f3b3..a4780e78ad5 100644 --- a/components/submit-expense/form/ExpenseCategorySection.tsx +++ b/components/submit-expense/form/ExpenseCategorySection.tsx @@ -5,15 +5,14 @@ import { FormattedMessage } from 'react-intl'; import useLoggedInUser from '../../../lib/hooks/useLoggedInUser'; import AccountingCategorySelect from '../../../components/AccountingCategorySelect'; +import { FormField } from '@/components/FormField'; import HTMLContent from '../../HTMLContent'; -import StyledInputFormikField from '../../StyledInputFormikField'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../../ui/Collapsible'; import { Step } from '../SubmitExpenseFlowSteps'; import type { ExpenseForm } from '../useExpenseForm'; import { FormSectionContainer } from './FormSectionContainer'; -import { FormField } from '@/components/FormField'; type ExpenseCategorySectionProps = { form: ExpenseForm; diff --git a/components/submit-expense/form/FormSectionContainer.tsx b/components/submit-expense/form/FormSectionContainer.tsx index 69c7bbe661e..dddc575ebb6 100644 --- a/components/submit-expense/form/FormSectionContainer.tsx +++ b/components/submit-expense/form/FormSectionContainer.tsx @@ -25,7 +25,7 @@ export function FormSectionContainer(props: FormSectionContainerProps) { const stepSubtitle = StepSubtitles[props.step]; return ( -
    +
    {props.title || }
    From ce8cd2addc7ab97a7fd078a4f882cb128147a6d7 Mon Sep 17 00:00:00 2001 From: Gustav Larsson Date: Wed, 22 Jan 2025 15:59:48 +0100 Subject: [PATCH 6/7] Fix AccountingCategorySelect instances to comply with it now using ui/Button --- components/AccountingCategorySelect.tsx | 6 +++--- .../dashboard/sections/expenses/HostCreateExpenseModal.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/AccountingCategorySelect.tsx b/components/AccountingCategorySelect.tsx index 360245f4b9c..00fe1074681 100644 --- a/components/AccountingCategorySelect.tsx +++ b/components/AccountingCategorySelect.tsx @@ -332,7 +332,7 @@ const AccountingCategorySelect = ({ allowNone = false, showCode = false, expenseValues = undefined, - buttonClassName = 'rounded-lg', + buttonClassName = '', children = null, selectFirstOptionIfSingle, disabled, @@ -383,7 +383,7 @@ const AccountingCategorySelect = ({ id={id} variant="outline" className={cn( - 'w-full max-w-[300px]', + 'w-full max-w-[300px] font-normal', { 'ring-2 ring-destructive ring-offset-2': error, }, @@ -399,7 +399,7 @@ const AccountingCategorySelect = ({ {getCategoryLabel(intl, selectedCategory, false, valuesByRole) || intl.formatMessage({ defaultMessage: 'Select category', id: 'RUJYth' })} - + )} diff --git a/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx b/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx index 504a3b21ee5..5ee764224b4 100644 --- a/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx +++ b/components/dashboard/sections/expenses/HostCreateExpenseModal.tsx @@ -384,7 +384,7 @@ export const HostCreateExpenseModal = ({ expenseValues={values} selectedCategory={field.value} onChange={category => setFieldValue(field.name, category)} - buttonClassName="rounded max-w-full" + buttonClassName="max-w-full" predictionStyle="inline-preload" showCode allowNone From bb5aa85a420c81d70628d46dda3e91235198fbc5 Mon Sep 17 00:00:00 2001 From: Gustav Larsson Date: Wed, 22 Jan 2025 16:01:17 +0100 Subject: [PATCH 7/7] Fix unused exports --- components/submit-expense/SubmitExpenseFlowSteps.tsx | 10 +--------- components/submit-expense/form/ExpenseItemsSection.tsx | 3 +-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/components/submit-expense/SubmitExpenseFlowSteps.tsx b/components/submit-expense/SubmitExpenseFlowSteps.tsx index d55e87baa14..268af43c791 100644 --- a/components/submit-expense/SubmitExpenseFlowSteps.tsx +++ b/components/submit-expense/SubmitExpenseFlowSteps.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { Path } from 'dot-path-value'; import { useFormikContext } from 'formik'; import { get, isEmpty } from 'lodash'; -import type { IntlShape, MessageDescriptor } from 'react-intl'; +import type { MessageDescriptor } from 'react-intl'; import { defineMessage, FormattedMessage } from 'react-intl'; import { cn } from '../../lib/utils'; @@ -103,14 +103,6 @@ function isExpenseFormStepCompleted(form: ExpenseForm, step: Step): boolean { return valueKeys.map(valueKey => isEmpty(get(form.errors, valueKey))).every(Boolean); } -export function expenseFormStepError(intl: IntlShape, form: ExpenseForm, step: Step): string { - if (expenseFormStepHasError(form, step) && isExpenseFormStepTouched(form, step) && form.submitCount > 0) { - return intl.formatMessage({ defaultMessage: 'Required', id: 'Seanpx' }); - } - - return null; -} - function expenseFormStepHasError(form: ExpenseForm, step: Step): boolean { return !isExpenseFormStepCompleted(form, step); } diff --git a/components/submit-expense/form/ExpenseItemsSection.tsx b/components/submit-expense/form/ExpenseItemsSection.tsx index 4f6056536df..cf6807f828e 100644 --- a/components/submit-expense/form/ExpenseItemsSection.tsx +++ b/components/submit-expense/form/ExpenseItemsSection.tsx @@ -479,10 +479,9 @@ function Taxes(props: { form: ExpenseForm }) { )} inputType="number" > - {({ field, hasError }) => ( + {({ field }) => ( props.form.setFieldValue(