diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c1db2c4..55d0ebf 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -42,7 +42,9 @@ module.exports = { plugins: ['react-refresh', '@typescript-eslint', 'react', 'prettier'], rules: { 'react-refresh/only-export-components': 'warn', + 'import/order': 'warn', 'react/react-in-jsx-scope': 'off', + 'react/jsx-curly-brace-presence': 'off', 'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }], 'import/extensions': [ 'error', diff --git a/Dockerfile b/Dockerfile index fa17e4d..3cddbb3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,7 @@ RUN yarn install RUN yarn build FROM nginx:1.25.1-alpine +ARG client_max_body_size COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist /usr/share/nginx/html EXPOSE 80 diff --git a/docker-compose.yml b/docker-compose.yml index f824401..f971586 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,9 @@ services: frontend: - build: ./credere-frontend/ + build: + context: ./credere-frontend/ + args: + VITE_MAX_FILE_SIZE_MB: ${VITE_MAX_FILE_SIZE_MB} environment: - VITE_APP_VERSION=${VITE_APP_VERSION} - VITE_API_URL=${VITE_API_URL} @@ -32,9 +35,10 @@ services: - HASH_KEY=${HASH_KEY} - TEST_MAIL_RECEIVER=${TEST_MAIL_RECEIVER} - IMAGES_BASE_URL=${IMAGES_BASE_URL} - - IMAGES_LANG_SUBPATH=${IMAGES_LANG_SUBPATH} + - EMAIL_TEMPLATE_LANG=${EMAIL_TEMPLATE_LANG} - FACEBOOK_LINK=${FACEBOOK_LINK} - TWITTER_LINK=${TWITTER_LINK} + - SENTRY_DSN=${SENTRY_DSN} - LINK_LINK=${LINK_LINK} - OCP_EMAIL_GROUP=${OCP_EMAIL_GROUP} - APPLICATION_EXPIRATION_DAYS=${APPLICATION_EXPIRATION_DAYS} @@ -44,6 +48,8 @@ services: - REMINDER_DAYS_BEFORE_EXPIRATION=${REMINDER_DAYS_BEFORE_EXPIRATION} - PROGRESS_TO_REMIND_STARTED_APPLICATIONS=${PROGRESS_TO_REMIND_STARTED_APPLICATIONS} - ENVIRONMENT=${ENVIRONMENT} + - TRANSIFEX_TOKEN=${TRANSIFEX_TOKEN} + - TRANSIFEX_SECRET=${TRANSIFEX_SECRET} extra_hosts: - 'host.docker.internal:host-gateway' diff --git a/nginx/nginx.conf b/nginx/nginx.conf index f65b1c0..ea84f61 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,6 +1,7 @@ server { listen 80; server_name frontend; + client_max_body_size 10M; location / { # This would be the directory where your React app's static files are stored at root /usr/share/nginx/html; diff --git a/public/images/es/set_password.png b/public/images/es/set_password.png new file mode 100644 index 0000000..659672c Binary files /dev/null and b/public/images/es/set_password.png differ diff --git a/src/api/private.ts b/src/api/private.ts index fc982a1..614416c 100644 --- a/src/api/private.ts +++ b/src/api/private.ts @@ -132,6 +132,14 @@ export const downloadDocumentFn = async (id: number) => { return response.data; }; +export const downloadApplicationFn = async (id: number, lang: string) => { + const response = await authApi.get(`applications/${id}/download-application/${lang}`, { + responseType: 'blob', + }); + + return response.data; +}; + export const emailToSME = async (emailToSMEPayload: EmailToSMEInput) => { const { application_id, ...payload } = emailToSMEPayload; const response = await authApi.post(`applications/email-sme/${application_id}`, payload); diff --git a/src/components/ApplicationAwardTable.tsx b/src/components/ApplicationAwardTable.tsx index 7b44ba7..0bab364 100644 --- a/src/components/ApplicationAwardTable.tsx +++ b/src/components/ApplicationAwardTable.tsx @@ -3,9 +3,10 @@ import { Paper, Table, TableBody, TableContainer, TableHead, TableRow } from '@m import { useT } from '@transifex/react'; import useGetPreviousAwards from '../hooks/useGetPreviousAwards'; +import useLocalizedDateFormatter from '../hooks/useLocalizedDateFormatter'; import useUpdateAward from '../hooks/useUpdateAward'; import { IApplication, IUpdateAward } from '../schemas/application'; -import { formatCurrency, formatDateFromString, formatPaymentMethod } from '../util'; +import { formatCurrency, formatPaymentMethod } from '../util'; import ApplicationTableDataAwardRow from './ApplicationTableDataAwardRow'; import ApplicationTableDataPreviousAwardRow from './ApplicationTableDataPreviousAwardRow'; import { DataTableHeadCell, DataTableHeadLabel } from './DataTable'; @@ -18,6 +19,7 @@ export interface ApplicationAwardTableProps { export function ApplicationAwardTable({ application, readonly = false, className }: ApplicationAwardTableProps) { const t = useT(); + const { formatDateFromString } = useLocalizedDateFormatter(); const { updateAwardMutation, isLoading } = useUpdateAward(); const { data: previousAwards, isLoading: isLoadingPreviousAwards } = useGetPreviousAwards(application.id); @@ -58,6 +60,7 @@ export function ApplicationAwardTable({ application, readonly = false, className name="title" label={t('Award Title')} award={award} + modifiedFields={application.modified_data_fields?.award_updates} /> `${award.award_currency} ${formatCurrency(value, award.award_currency)}`} + modifiedFields={application.modified_data_fields?.award_updates} /> diff --git a/src/components/ApplicationTableDataAwardRow.tsx b/src/components/ApplicationTableDataAwardRow.tsx index 445196f..5895f7f 100644 --- a/src/components/ApplicationTableDataAwardRow.tsx +++ b/src/components/ApplicationTableDataAwardRow.tsx @@ -19,6 +19,7 @@ export function ApplicationTableDataAwardRow({ updateValue, isLoading, readonly, + modifiedFields, }: ApplicationTableAwardDataRowProps) { const value = award[name]; const missing = missingData[name]; @@ -28,7 +29,13 @@ export function ApplicationTableDataAwardRow({ {label} - + {!missing && {formattedValue}} {missing && updateValue && ( @@ -54,6 +61,7 @@ ApplicationTableDataAwardRow.defaultProps = { preWhitespace: false, type: undefined, formLabel: undefined, + modifiedFields: undefined, }; export default ApplicationTableDataAwardRow; diff --git a/src/components/ApplicationTableDataBorrowerRow.tsx b/src/components/ApplicationTableDataBorrowerRow.tsx index 74fbfd6..4329d6f 100644 --- a/src/components/ApplicationTableDataBorrowerRow.tsx +++ b/src/components/ApplicationTableDataBorrowerRow.tsx @@ -25,6 +25,7 @@ export function ApplicationTableDataBorrowerRow({ withoutVerify, isLoading, readonly, + modifiedFields, }: ApplicationTableBorrowerDataRowProps) { const t = useT(); @@ -46,8 +47,10 @@ export function ApplicationTableDataBorrowerRow({ {(!missing || withoutVerify) && ( @@ -87,6 +90,7 @@ ApplicationTableDataBorrowerRow.defaultProps = { type: undefined, formLabel: undefined, withoutVerify: false, + modifiedFields: undefined, }; export default ApplicationTableDataBorrowerRow; diff --git a/src/components/ApplicationTableDataDocumentRow.tsx b/src/components/ApplicationTableDataDocumentRow.tsx index 7958411..f8bd25d 100644 --- a/src/components/ApplicationTableDataDocumentRow.tsx +++ b/src/components/ApplicationTableDataDocumentRow.tsx @@ -1,5 +1,5 @@ - import { TableRow } from '@mui/material'; +import { useT } from '@transifex/react'; import DocumentIcon from 'src/assets/icons/document.svg'; import { DOCUMENT_TYPES_NAMES } from '../constants'; @@ -18,6 +18,7 @@ export function ApplicationTableDataDocumentRow({ isLoading = false, readonly = false, }: ApplicationTableDocumentDataRowProps) { + const t = useT(); const value = document.name; const missing = false; @@ -30,9 +31,9 @@ export function ApplicationTableDataDocumentRow({ return ( - {DOCUMENT_TYPES_NAMES[document.type]} + {t(DOCUMENT_TYPES_NAMES[document.type])} - + {!downloadDocument && ( {formattedValue} diff --git a/src/components/ApplicationTableDataPreviousAwardRow.tsx b/src/components/ApplicationTableDataPreviousAwardRow.tsx index dc1a898..365b832 100644 --- a/src/components/ApplicationTableDataPreviousAwardRow.tsx +++ b/src/components/ApplicationTableDataPreviousAwardRow.tsx @@ -1,13 +1,13 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Box, Paper, Table, TableBody, TableContainer, TableHead, TableRow } from '@mui/material'; import { useT } from '@transifex/react'; import { useState } from 'react'; import Minus from 'src/assets/icons/minus.svg'; import Plus from 'src/assets/icons/plus.svg'; +import useLocalizedDateFormatter from '../hooks/useLocalizedDateFormatter'; import { IAward } from '../schemas/application'; import Loader from '../stories/loader/Loader'; -import { formatCurrency, formatDateFromString } from '../util'; +import { formatCurrency } from '../util'; import { ApplicationTableAwardDataRowProps } from './ApplicationTableDataRow'; import DataAvailability from './DataAvailability'; import { DataTableCell, DataTableHeadCell, DataTableHeadLabel } from './DataTable'; @@ -36,6 +36,7 @@ export function ApplicationTableDataPreviousAwardRow({ preWhitespace, }: ApplicationTableDataPreviousAwardRowProps) { const t = useT(); + const { formatDateFromString } = useLocalizedDateFormatter(); const [open, setOpen] = useState(false); @@ -49,7 +50,7 @@ export function ApplicationTableDataPreviousAwardRow({ {label} - + {!missing && ( diff --git a/src/components/ApplicationTableDataRow.tsx b/src/components/ApplicationTableDataRow.tsx index a0f153b..4533569 100644 --- a/src/components/ApplicationTableDataRow.tsx +++ b/src/components/ApplicationTableDataRow.tsx @@ -1,5 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { IAward, IBorrower, IBorrowerDocument, IUpdateAward, IUpdateBorrower } from '../schemas/application'; +import { + IAward, + IBorrower, + IBorrowerDocument, + IModifiedDataFields, + IUpdateAward, + IUpdateBorrower, +} from '../schemas/application'; export interface ApplicationTableDataRowProps { label: string; @@ -10,6 +17,7 @@ export interface ApplicationTableDataRowProps { formatter?: (value: any) => string; isLoading: boolean; readonly: boolean; + modifiedFields?: { [key: string]: IModifiedDataFields }; } export interface ApplicationTableAwardDataRowProps extends ApplicationTableDataRowProps { diff --git a/src/components/ApplicationsList.tsx b/src/components/ApplicationsList.tsx index a3a6202..01375d0 100644 --- a/src/components/ApplicationsList.tsx +++ b/src/components/ApplicationsList.tsx @@ -17,6 +17,7 @@ import { STARTED_STATUS, USER_TYPES, } from '../constants'; +import useDownloadApplication from '../hooks/useDownloadApplication'; import useStartApplication from '../hooks/useStartApplication'; import { EXTENDED_APPLICATION_FROM, @@ -26,6 +27,7 @@ import { PaginationInput, } from '../schemas/application'; import LinkButton from '../stories/link-button/LinkButton'; +import Loader from '../stories/loader/Loader'; import { renderApplicationStatus } from '../util'; import { DataTable, HeadCell, Order } from './DataTable'; @@ -55,9 +57,8 @@ const headCellsBase: HeadCell[] = [ disablePadding: false, label: t('Stage'), sortable: true, - render: (row: IApplication & IExtendedApplication, headCell: HeadCell) => ( - <>{renderApplicationStatus(String(row[headCell.id]))} - ), + render: (row: IApplication & IExtendedApplication, headCell: HeadCell) => + renderApplicationStatus(String(row[headCell.id])), }, ]; @@ -74,12 +75,18 @@ const headCellsOCP: HeadCell[] = [ type ExtendendApplication = IApplication & IExtendedApplication; const ApplicationDataTable = DataTable; -const actionsFIBase = (row: ExtendendApplication, onStartApplicationHandler: (id: number) => void) => ( +const actionsFIBase = ( + row: ExtendendApplication, + isLoading: boolean, + onStartApplicationHandler: (id: number) => void, + onDownloadApplicationHandler: (id: number) => void, +) => ( {STARTED_STATUS.includes(row.status) && ( onStartApplicationHandler(row.id)} label={t('Start')} + disabled={isLoading} size="small" noIcon /> @@ -101,6 +109,7 @@ const actionsFIBase = (row: ExtendendApplication, onStartApplicationHandler: (id component={Link} to={`/applications/${row.id}/complete-application`} label={t('Review')} + disabled={isLoading} size="small" noIcon /> @@ -111,15 +120,16 @@ const actionsFIBase = (row: ExtendendApplication, onStartApplicationHandler: (id component={Link} to={`/applications/${row.id}/view`} label={t('View')} + disabled={isLoading} size="small" noIcon /> )} onDownloadApplicationHandler(row.id)} label={t('Download')} + disabled={isLoading} size="small" noIcon /> @@ -156,6 +166,10 @@ interface ApplicationListProps { export function ApplicationList({ type }: ApplicationListProps) { const { enqueueSnackbar } = useSnackbar(); const { startApplicationMutation } = useStartApplication(); + const [idToDownload, setIdToDownload] = useState(); + + const { downloadedApplication, isLoading } = useDownloadApplication(idToDownload); + const [payload, setPayload] = useState({ page: 0, page_size: PAGE_SIZES[0], @@ -235,20 +249,59 @@ export function ApplicationList({ type }: ApplicationListProps) { [startApplicationMutation], ); - const actionsFI = (row: ExtendendApplication) => actionsFIBase(row, onStartApplicationHandler); + useEffect(() => { + if (downloadedApplication) { + const href = window.URL.createObjectURL(downloadedApplication); + + const anchorElement = document.createElement('a'); + + anchorElement.href = href; + const filename = `${t('application')}-${idToDownload}.zip`; + anchorElement.download = filename; + + document.body.appendChild(anchorElement); + anchorElement.click(); + + document.body.removeChild(anchorElement); + window.URL.revokeObjectURL(href); + setIdToDownload(undefined); + } + }, [downloadedApplication, idToDownload]); + + const onDownloadApplicationHandler = useCallback( + (id: number) => { + setIdToDownload(id); + }, + [setIdToDownload], + ); + + // eslint-disable-next-line no-shadow + const actionsFI = (row: ExtendendApplication, isLoading?: boolean) => + actionsFIBase( + row, + isLoading !== undefined ? isLoading : false, + onStartApplicationHandler, + onDownloadApplicationHandler, + ); return ( - + <> + {isLoading && } + {!isLoading && ( + + )} + ); } diff --git a/src/components/Charts.tsx b/src/components/Charts.tsx index a32e9fd..b512a86 100644 --- a/src/components/Charts.tsx +++ b/src/components/Charts.tsx @@ -1,4 +1,4 @@ -/* eslint-disable react/require-default-props */ + /* eslint-disable react/no-array-index-key */ @@ -32,7 +32,7 @@ export function ChartPie({ data }: ChartsProps) { {data.map((_entry, index) => ( - // eslint-disable-next-line react/no-array-index-key + ))} diff --git a/src/components/CreditProductConfirmation.tsx b/src/components/CreditProductConfirmation.tsx index 91386c9..159ac70 100644 --- a/src/components/CreditProductConfirmation.tsx +++ b/src/components/CreditProductConfirmation.tsx @@ -2,9 +2,10 @@ import { Paper, Table, TableBody, TableContainer, TableHead, TableRow } from '@m import { useT } from '@transifex/react'; import { CREDIT_PRODUCT_TYPE } from '../constants'; +import useLocalizedDateFormatter from '../hooks/useLocalizedDateFormatter'; import { IApplication, ICreditProduct } from '../schemas/application'; import Title from '../stories/title/Title'; -import { formatCurrency, formatDateFromString } from '../util'; +import { formatCurrency } from '../util'; import { DataTableCell, DataTableHeadCell, DataTableHeadLabel } from './DataTable'; export interface CreditProductConfirmationProps { @@ -14,6 +15,7 @@ export interface CreditProductConfirmationProps { export function CreditProductConfirmation({ creditProduct, application }: CreditProductConfirmationProps) { const t = useT(); + const { formatDateFromString } = useLocalizedDateFormatter(); const isLoan = creditProduct.type === CREDIT_PRODUCT_TYPE.LOAN; diff --git a/src/components/CreditProductList.tsx b/src/components/CreditProductList.tsx index a3846d8..7ec1308 100644 --- a/src/components/CreditProductList.tsx +++ b/src/components/CreditProductList.tsx @@ -1,4 +1,3 @@ - import { t } from '@transifex/native'; import { Link } from 'react-router-dom'; @@ -13,7 +12,7 @@ const headCells: HeadCell[] = [ disablePadding: false, label: t('Borrower size'), sortable: false, - render: (row: ICreditProduct) => <>{renderBorrowerSizeType(row.borrower_size)}, + render: (row: ICreditProduct) => renderBorrowerSizeType(row.borrower_size), }, { id: 'lower_limit', @@ -41,7 +40,7 @@ const headCells: HeadCell[] = [ disablePadding: false, label: t('Type'), sortable: false, - render: (row: ICreditProduct) => <>{renderCreditProductType(row.type)}, + render: (row: ICreditProduct) => renderCreditProductType(row.type), }, ]; diff --git a/src/components/CreditProductReview.tsx b/src/components/CreditProductReview.tsx index e4c81fa..b6d9e44 100644 --- a/src/components/CreditProductReview.tsx +++ b/src/components/CreditProductReview.tsx @@ -2,9 +2,10 @@ import { Paper, Table, TableBody, TableContainer, TableHead, TableRow } from '@m import { useT } from '@transifex/react'; import { CREDIT_PRODUCT_TYPE } from '../constants'; +import useLocalizedDateFormatter from '../hooks/useLocalizedDateFormatter'; import { IApplication } from '../schemas/application'; import Title from '../stories/title/Title'; -import { formatCurrency, formatDateFromString } from '../util'; +import { formatCurrency } from '../util'; import { DataTableCell, DataTableHeadCell, DataTableHeadLabel } from './DataTable'; export interface CreditProductReviewProps { @@ -14,6 +15,8 @@ export interface CreditProductReviewProps { export function CreditProductReview({ application, className }: CreditProductReviewProps) { const t = useT(); + const { formatDateFromString } = useLocalizedDateFormatter(); + const creditProduct = application.credit_product; if (!creditProduct) return null; diff --git a/src/components/DataAvailability.tsx b/src/components/DataAvailability.tsx index ba18e88..65a4bb9 100644 --- a/src/components/DataAvailability.tsx +++ b/src/components/DataAvailability.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import CheckGreen from '../assets/icons/check-green.svg'; import WarnRed from '../assets/icons/warn-red.svg'; +import useLocalizedDateFormatter from '../hooks/useLocalizedDateFormatter'; +import { IModifiedDataFields } from '../schemas/application'; import Text from '../stories/text/Text'; const getIcon = (available: boolean, name: string) => { @@ -18,22 +20,27 @@ const getIcon = (available: boolean, name: string) => { interface DataAvailabilityProps { available: boolean; - name: string; + name?: string; + label: string; readonly: boolean; + modifiedFields?: { [key: string]: IModifiedDataFields }; } -export function DataAvailability({ available, name, readonly }: DataAvailabilityProps) { +export function DataAvailability({ available, name, label, readonly, modifiedFields }: DataAvailabilityProps) { const t = useT(); + const { formatDateFromString } = useLocalizedDateFormatter(); const [open, setOpen] = useState(false); const handleToggle = () => { setOpen(!open); }; + const modified = modifiedFields && name ? modifiedFields[name] : undefined; + if (available || readonly) { return ( - {getIcon(available, name)} + {getIcon(available, label)} {available ? t('Yes') : t('Data missing')} @@ -49,7 +56,7 @@ export function DataAvailability({ available, name, readonly }: DataAvailability sx={{ pt: '2px', }}> - {getIcon(available, name)} + {getIcon(available, label)} @@ -63,10 +70,20 @@ export function DataAvailability({ available, name, readonly }: DataAvailability - {t( - 'Data for the {fieldName} for the SME is not available. Confirm manually through SECOP or alternative source.', - { fieldName: name }, - )} + {!modified + ? t( + 'Data for the {fieldName} for the SME is not available. Confirm manually through SECOP or alternative source.', + { fieldName: label }, + ) + : t( + 'Data for the {fieldName} was not available, but completed by {userType} user {userName} on {modifiedDate}.', + { + fieldName: label, + userType: modified.user_type, + userName: modified.user, + modifiedDate: formatDateFromString(modified.modified_at), + }, + )} @@ -76,4 +93,9 @@ export function DataAvailability({ available, name, readonly }: DataAvailability ); } +DataAvailability.defaultProps = { + modifiedFields: undefined, + name: undefined, +}; + export default DataAvailability; diff --git a/src/components/DataTable.tsx b/src/components/DataTable.tsx index b066324..e154ba6 100644 --- a/src/components/DataTable.tsx +++ b/src/components/DataTable.tsx @@ -18,7 +18,8 @@ import SorterIcon from 'src/assets/icons/sorter.svg'; import { twMerge } from 'tailwind-merge'; import { PAGE_SIZES } from '../constants'; -import { formatCurrency, formatDateFromString } from '../util'; +import useLocalizedDateFormatter from '../hooks/useLocalizedDateFormatter'; +import { formatCurrency } from '../util'; function Sorter() { return sorter-icon; @@ -70,7 +71,7 @@ export interface HeadCell { id: Extract; label: string; type?: DataCellType; - render?: (row: T, headCell: HeadCell) => JSX.Element; + render?: (row: T, headCell: HeadCell) => JSX.Element | string; sortable?: boolean; width?: number; } @@ -155,7 +156,7 @@ function DataTableHead({ className="flex flex-row justify-between text-moodyBlue text-sm font-normal" IconComponent={orderBy === headCell.id ? SorterDirection : Sorter} onClick={createSortHandler(headCell.id)}> - {headCell.label} + {t(headCell.label)} )} {!headCell.sortable && } @@ -186,7 +187,8 @@ export interface DataTableProps { useEmptyRows?: boolean; handleRequestSort?: (property: Extract, sortOrder: Order) => void; pagination?: HandlePagination; - actions?: (row: T) => JSX.Element; + actions?: (row: T, isLoading?: boolean) => JSX.Element; + isLoading?: boolean; } function renderValue(row: T, headCell: HeadCell) { @@ -194,10 +196,6 @@ function renderValue(row: T, headCell: HeadCell) { return headCell.render(row, headCell); } - if (headCell.type === 'date') { - return formatDateFromString(String(row[headCell.id])); - } - if (headCell.type === 'currency') { return formatCurrency(Number(row[headCell.id])); } @@ -211,8 +209,10 @@ export function DataTable({ handleRequestSort, pagination, actions, + isLoading, }: DataTableProps) { const t = useT(); + const { formatDateFromString } = useLocalizedDateFormatter(); const [visibleRows, setVisibleRows] = React.useState(rows); const [order, setOrder] = React.useState('asc'); const [orderBy, setOrderBy] = React.useState | undefined>(); @@ -276,10 +276,11 @@ export function DataTable({ {headCells.map((headCell) => ( - {renderValue(row, headCell)} + {headCell.type !== 'date' && t(renderValue(row, headCell))} + {headCell.type === 'date' && formatDateFromString(String(row[headCell.id]))} ))} - {actions && {actions(row)}} + {actions && {actions(row, isLoading)}} ))} @@ -327,6 +328,7 @@ DataTable.defaultProps = { pagination: undefined, useEmptyRows: true, actions: undefined, + isLoading: false, }; export default DataTable; diff --git a/src/components/DocumentField.tsx b/src/components/DocumentField.tsx index b8dd9ed..d8c03a0 100644 --- a/src/components/DocumentField.tsx +++ b/src/components/DocumentField.tsx @@ -31,6 +31,7 @@ export function DocumentField({ label, documentType, secure = false, className, const { enqueueSnackbar } = useSnackbar(); const [current, setCurrent] = useState(); const [showUploader, setShowUploader] = useState(true); + const [loading, setLoading] = useState(false); const applicationContext = useApplicationContext(); const secureApplicationContext = useSecureApplicationContext(); @@ -68,6 +69,7 @@ export function DocumentField({ label, documentType, secure = false, className, if (!application) return; try { + setLoading(true); if (documentType === DOCUMENTS_TYPE.COMPLIANCE_REPORT) { const payload: UploadComplianceInput = { file, @@ -94,7 +96,7 @@ export function DocumentField({ label, documentType, secure = false, className, const uploaded = await uploadFileFn(payload); setCurrent(uploaded); } - + setLoading(false); setShowUploader(false); if (setUploadState) setUploadState((prev) => ({ ...prev, [documentType]: true })); } catch (error) { @@ -139,7 +141,7 @@ export function DocumentField({ label, documentType, secure = false, className, /> )} - {showUploader && } + {showUploader && } ); } diff --git a/src/components/FAQComponent.tsx b/src/components/FAQComponent.tsx index 96e10ce..8550472 100644 --- a/src/components/FAQComponent.tsx +++ b/src/components/FAQComponent.tsx @@ -12,7 +12,7 @@ interface FAQComponentProps { export function FAQComponent({ className }: FAQComponentProps) { const t = useT(); return ( - + {t( 'Guaranteed loans give high-risk borrowers a way to access financing, and provide protection for the lender.', diff --git a/src/components/FileUploader.tsx b/src/components/FileUploader.tsx index 98c6b54..ff417e9 100644 --- a/src/components/FileUploader.tsx +++ b/src/components/FileUploader.tsx @@ -14,8 +14,11 @@ import Cloud from 'src/assets/icons/cloud.svg'; import Button from 'src/stories/button/Button'; import Text from 'src/stories/text/Text'; +import { Progress } from '../stories/loader/Loader'; + interface FileUploaderProps { className?: string; + loading: boolean; onAcceptedFile: (file: File) => void; } @@ -27,7 +30,7 @@ const errorsMap = { [ErrorCode.FileTooLarge]: tNative('File is larger than {maxSizeMB} MB', { maxSizeMB }), }; -export function FileUploader({ className, onAcceptedFile }: FileUploaderProps) { +export function FileUploader({ className, loading, onAcceptedFile }: FileUploaderProps) { const t = useT(); const { enqueueSnackbar } = useSnackbar(); @@ -67,29 +70,32 @@ export function FileUploader({ className, onAcceptedFile }: FileUploaderProps) {
e.stopPropagation()}> -