Skip to content
15 changes: 8 additions & 7 deletions apps/storefront/src/components/upload/B3Upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Alert, Box, Link, useTheme } from '@mui/material';
import Grid from '@mui/material/Unstable_Grid2';

import { useMobile } from '@/hooks/useMobile';
import { useB3Lang } from '@/lib/lang';
import { ValidProductItem } from '@/pages/QuickOrder/components/ValidProduct';
import {
B2BProductsBulkUploadCSV,
Expand Down Expand Up @@ -64,7 +65,7 @@ export function B3Upload(props: B3UploadProps) {
const {
isOpen,
setIsOpen,
bulkUploadTitle = 'Bulk upload',
bulkUploadTitle,
addBtnText = 'add to list',
handleAddToList = async () => {},
setProductData = () => {},
Expand All @@ -74,6 +75,7 @@ export function B3Upload(props: B3UploadProps) {
} = props;

const [isMobile] = useMobile();
const b3Lang = useB3Lang();

const uploadRef = useRef<HTMLInputElement>(null);

Expand Down Expand Up @@ -279,7 +281,7 @@ export function B3Upload(props: B3UploadProps) {
color: '#5E637A',
}}
>
Drag & drop file here
{b3Lang('global.B3Upload.dragAndDrop')}
</Box>
</Grid>
</div>
Expand All @@ -301,7 +303,7 @@ export function B3Upload(props: B3UploadProps) {
whiteSpace: 'nowrap',
}}
>
File types: CSV, maximum size: 50MB.
{b3Lang('global.B3Upload.fileTypesInfo')}
</Box>
<Box
sx={{
Expand All @@ -319,14 +321,14 @@ export function B3Upload(props: B3UploadProps) {
pointerEvents: 'auto',
}}
>
Download sample
{b3Lang('global.B3Upload.downloadSample')}
</Link>
</Box>
</Grid>

<Grid display="flex" justifyContent="center" xs={12}>
<CustomButton variant="outlined" onClick={openFile} sx={{ pointerEvents: 'auto' }}>
Upload file
{b3Lang('global.B3Upload.uploadFile')}
</CustomButton>
</Grid>
</Grid>
Expand All @@ -341,10 +343,9 @@ export function B3Upload(props: B3UploadProps) {
return (
<B3Dialog
isOpen={isOpen}
title={bulkUploadTitle}
title={bulkUploadTitle || b3Lang('global.B3Upload.title')}
maxWidth="lg"
rightSizeBtn={addBtnText}
leftSizeBtn="cancel"
handleLeftClick={() => {
setStep('init');
setIsOpen(false);
Expand Down
30 changes: 19 additions & 11 deletions apps/storefront/src/components/upload/BulkUploadTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function BulkUploadTable(props: BulkUploadTableProps) {
const columnErrorsItems: TableColumnItem<ListItem>[] = [
{
key: 'sku',
title: 'SKU',
title: b3Lang('global.bulkUpload.columnSku'),
width: '25%',
render: (row) => (
<Typography
Expand All @@ -56,7 +56,7 @@ function BulkUploadTable(props: BulkUploadTableProps) {
},
{
key: 'qty',
title: 'Qty',
title: b3Lang('global.bulkUpload.columnQty'),
width: '20%',
render: (row) => (
<Typography
Expand All @@ -70,7 +70,7 @@ function BulkUploadTable(props: BulkUploadTableProps) {
},
{
key: 'row',
title: 'Row',
title: b3Lang('global.bulkUpload.columnRow'),
width: '20%',
render: (row) => (
<Typography
Expand All @@ -84,7 +84,7 @@ function BulkUploadTable(props: BulkUploadTableProps) {
},
{
key: 'error',
title: 'Error',
title: b3Lang('global.bulkUpload.columnError'),
width: '35%',
render: (row) => (
<Typography
Expand All @@ -101,7 +101,7 @@ function BulkUploadTable(props: BulkUploadTableProps) {
const columnValidItems: TableColumnItem<ListItem>[] = [
{
key: 'sku',
title: 'SKU',
title: b3Lang('global.bulkUpload.columnSku'),
width: '50%',
render: (row) => (
<Typography
Expand All @@ -115,7 +115,7 @@ function BulkUploadTable(props: BulkUploadTableProps) {
},
{
key: 'qty',
title: 'Qty',
title: b3Lang('global.bulkUpload.columnQty'),
width: '50%',
render: (row) => (
<Typography
Expand Down Expand Up @@ -225,7 +225,7 @@ function BulkUploadTable(props: BulkUploadTableProps) {
fontSize: '14px',
}}
>
Remove
{b3Lang('global.bulkUpload.remove')}
</MenuItem>
</Menu>
</Box>
Expand All @@ -244,13 +244,21 @@ function BulkUploadTable(props: BulkUploadTableProps) {
{errorProduct.length > 0 && (
<Tab
value="error"
label={errorProduct.length ? `Errors (${errorProduct.length})` : 'Errors'}
label={
errorProduct.length
? `${b3Lang('global.bulkUpload.tabErrors')} (${errorProduct.length})`
: b3Lang('global.bulkUpload.tabErrors')
}
/>
)}
{validProduct.length > 0 && (
<Tab
value="valid"
label={validProduct.length ? `Valid (${validProduct.length})` : 'Valid'}
label={
validProduct.length
? `${b3Lang('global.bulkUpload.tabValid')} (${validProduct.length})`
: b3Lang('global.bulkUpload.tabValid')
}
/>
)}
</Tabs>
Expand All @@ -262,9 +270,9 @@ function BulkUploadTable(props: BulkUploadTableProps) {
rowsPerPageOptions={[10, 20, 50]}
showBorder={!isMobile}
getRequestList={getProductInfo}
labelRowsPerPage="Products per page:"
labelRowsPerPage={b3Lang('global.bulkUpload.productsPerPage')}
itemIsMobileSpacing={0}
noDataText="No product"
noDataText={b3Lang('global.bulkUpload.noProduct')}
tableKey="row"
searchParams={{
activeTab,
Expand Down
15 changes: 15 additions & 0 deletions apps/storefront/src/lib/lang/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@
"global.companyHierarchy.externalBtn": "You are representing",
"global.companyCredit.alert": "Your account does not currently allow purchasing due to a credit hold. Please contact us to reconcile.",
"global.B3Upload.downloadErrorResults": "Download error results",
"global.searchProduct.noProductsFound": "No products found",
"global.B3Upload.title": "Bulk upload",
"global.B3Upload.dragAndDrop": "Drag & drop file here",
"global.B3Upload.fileTypesInfo": "File types: CSV, maximum size: 50MB.",
"global.B3Upload.downloadSample": "Download sample",
"global.B3Upload.uploadFile": "Upload file",
"global.bulkUpload.columnSku": "SKU",
"global.bulkUpload.columnQty": "Qty",
"global.bulkUpload.columnRow": "Row",
"global.bulkUpload.columnError": "Error",
"global.bulkUpload.tabErrors": "Errors",
"global.bulkUpload.tabValid": "Valid",
"global.bulkUpload.remove": "Remove",
"global.bulkUpload.productsPerPage": "Products per page:",
"global.bulkUpload.noProduct": "No product",
"global.statusNotifications.orderingWillBeEnabledAfterAccountApproval": "Your business account is pending approval. Ordering will be enabled after account approval.",
"global.statusNotifications.productsPricingAndOrderingWillBeEnabledAfterApproval": "Your business account is pending approval. Products, pricing, and ordering will be enabled after account approval.",
"global.statusNotifications.willGainAccessToBusinessFeatAfterApproval": "Your business account is pending approval. You will gain access to business account features after account approval.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export default function ProductListDialog(props: ProductListDialogProps) {
actionWidth="180px"
/>
) : (
<Typography>No products found</Typography>
<Typography>{b3Lang('global.searchProduct.noProductsFound')}</Typography>
)}
</Box>
</B3Spin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export default function ProductListDialog(props: ProductListDialogProps) {
actionWidth="180px"
/>
) : (
<Typography>No products found</Typography>
<Typography>{b3Lang('global.searchProduct.noProductsFound')}</Typography>
)}
</Box>
</B3Spin>
Expand Down
47 changes: 42 additions & 5 deletions apps/storefront/src/store/appAsyncThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,24 @@ interface GetPageTranslationsParams {
interface GetPageTranslationResponse {
pageTranslations: Record<string, string>;
page: string;
fetchedDependencyPages: string[];
}

const REPEATED_PAGES: Partial<Record<string, string>> = {
'company-orders': 'orders',
'purchased-products': 'purchasedProducts',
'user-management': 'userManagement',
};

/**
* Pages that render shared components whose translation keys belong to another page.
* When a page is visited, translations for its dependencies are also fetched.
*/
const TRANSLATION_DEPENDENCIES: Partial<Record<string, string[]>> = {
purchasedProducts: ['shoppingLists'],
shoppingList: ['purchasedProducts'],
quoteDraft: ['purchasedProducts', 'shoppingList', 'shoppingLists'],
quoteDetail: ['quoteDraft', 'purchasedProducts', 'shoppingList', 'shoppingLists'],
};

export const getGlobalTranslations = createAppAsyncThunk<
Expand Down Expand Up @@ -65,15 +79,38 @@ export const getPageTranslations = createAppAsyncThunk<
GetPageTranslationsParams
>(
'lang/getPageTranslations',
async ({ channelId, page: pageKey }, { rejectWithValue }) => {
async ({ channelId, page: pageKey }, { rejectWithValue, getState }) => {
const page = REPEATED_PAGES[pageKey] ?? pageKey;
const { message } = await getTranslation({ channelId, page });
const { fetchedPages } = getState().lang;
const dependencyPages = (TRANSLATION_DEPENDENCIES[page] ?? []).filter(
(dep) => !fetchedPages.includes(dep),
);

if (typeof message === 'string') {
return rejectWithValue(message);
const primaryResult = await getTranslation({ channelId, page });

if (typeof primaryResult.message === 'string') {
return rejectWithValue(primaryResult.message);
}

return { pageTranslations: message, page };
const dependencyResults = await Promise.allSettled(
dependencyPages.map((p) => getTranslation({ channelId, page: p })),
);

const successfulDeps = dependencyResults
.map((r, i) =>
r.status === 'fulfilled' && typeof r.value.message !== 'string'
? { page: dependencyPages[i], translations: r.value.message as Record<string, string> }
: null,
)
.filter(Boolean) as { page: string; translations: Record<string, string> }[];

const pageTranslations = Object.assign(
{},
primaryResult.message as Record<string, string>,
...successfulDeps.map((d) => d.translations),
);

return { pageTranslations, page, fetchedDependencyPages: successfulDeps.map((d) => d.page) };
},
{
condition: ({ page: pageKey }, { getState }) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/storefront/src/store/slices/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const langSlice = createSlice({
Object.entries(payload.pageTranslations).forEach(([key, translation]) => {
state.translations[key] = translation;
});
state.fetchedPages.push(payload.page);
state.fetchedPages.push(payload.page, ...payload.fetchedDependencyPages);
});
},
});
Expand Down