Skip to content

Commit

Permalink
Work on document design page
Browse files Browse the repository at this point in the history
  • Loading branch information
oneanotheruser committed Feb 6, 2025
1 parent fdb71ad commit 990b4da
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 63 deletions.
1 change: 1 addition & 0 deletions packages/sdk-react/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ module.exports = {
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.png$': '<rootDir>/src/mocks/fileMock.ts',
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { documentTemplateList } from '@/mocks/documentTemplates';
import { renderWithClient } from '@/utils/test-utils';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { requestFn } from '@openapi-qraft/react';
import { fireEvent, screen, waitFor, within } from '@testing-library/react';

import { DocumentDesign } from './DocumentDesign';

fdescribe('DocumentDesign', () => {
const requestFnMock = requestFn as jest.MockedFunction<typeof requestFn>;

describe('DocumentDesign', () => {
test('should render document design page', () => {
renderWithClient(<DocumentDesign />);

Expand Down Expand Up @@ -33,7 +36,59 @@ fdescribe('DocumentDesign', () => {
});

documentTemplateList.data.forEach((template) => {
expect(screen.getAllByText(template.name)[0]).toBeInTheDocument();
expect(
screen.getByTestId(`documentTemplate-${template.name}`)
).toBeInTheDocument();
});

const defaultTemplate = documentTemplateList.data.find(
(template) => template.is_default
);

expect(
within(
screen.getByTestId(`documentTemplate-${defaultTemplate?.name}`)
).getByText('Default')
).toBeInTheDocument();
});

describe('when use selects a new template', () => {
test('it enables `Set as default` button', async () => {
renderWithClient(<DocumentDesign />);

const selectTemplateButton = await screen.findByRole('button', {
name: 'Select template',
});

fireEvent.click(selectTemplateButton);

await waitFor(() => {
expect(screen.getByText('Document templates')).toBeInTheDocument();
});

const notDefaultTemplate = documentTemplateList.data.find(
(template) => !template.is_default
);

fireEvent.click(
screen.getByTestId(`documentTemplate-${notDefaultTemplate?.name}`)
);

const setAsDefaultButton = screen.getByRole('button', {
name: 'Set as default',
});

expect(setAsDefaultButton).toBeEnabled();

fireEvent.click(setAsDefaultButton);

await waitFor(() =>
expect(requestFnMock.mock.lastCall?.[0].url).toBe(
'/document_templates/{document_template_id}/make_default'
)
);

await waitFor(() => expect(setAsDefaultButton).toBeDisabled());
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useLingui } from '@lingui/react';
import { Box, Typography, Button, Stack } from '@mui/material';

import { DocumentDesignSelection } from './components/DocumentDesignSelection/DocumentDesignSelection';
import previewImg from './preview.svg';
import previewImg from './preview.png';

const DocumentDesignBase = () => {
const { i18n } = useLingui();
Expand All @@ -26,7 +26,7 @@ const DocumentDesignBase = () => {
borderRadius: 2,
border: '1px solid',
borderColor: 'divider',
background: 'linear-gradient(rgb(244, 240, 254), rgb(255, 255, 255))',
background: 'linear-gradient(#F4F0FE, #FFFFFF)',
display: 'flex',
justifyContent: 'space-between',
paddingBottom: 0,
Expand All @@ -37,10 +37,10 @@ const DocumentDesignBase = () => {
sx={{ justifyContent: 'space-between', paddingBottom: 2 }}
>
<Box>
<Typography variant="subtitle2">
<Typography variant="h3">
{t(i18n)`Set your document style`}
</Typography>
<Typography variant="caption">
<Typography variant="body1" sx={{ marginTop: 1 }}>
{t(i18n)`Various templates for your documents`}
</Typography>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { components } from '@/api';
import { MoniteScopedProviders } from '@/core/context/MoniteScopedProviders';
import { DialogContent, Stack, Box } from '@mui/material';

import { useDocumentTemplatePreviewLoader } from '../../useDocumentTemplatePreviewLoader';
import { useDocumentTemplatesApi } from '../../useDocumentTemplatesApi';
import { DocumentDesignSelectionHeader } from '../DocumentDesignSelectionHeader';
import { DocumentDesignTemplatePreview } from '../DocumentDesignTemplatePreview';
Expand All @@ -24,6 +25,7 @@ const DocumentDesignSelectionBase = () => {
isLoading,
setDefaultTemplate,
} = useDocumentTemplatesApi();
const { getPreview } = useDocumentTemplatePreviewLoader();

const [selectedTemplate, setSelectedTemplate] = useState<DocumentTemplate>();

Expand All @@ -45,14 +47,18 @@ const DocumentDesignSelectionBase = () => {
<Stack direction="row" sx={{ justifyContent: 'space-around' }}>
<Box sx={{ maxWidth: 595, width: '100%' }}>
{selectedTemplate && (
<DocumentDesignTemplatePreview template={selectedTemplate} />
<DocumentDesignTemplatePreview
template={selectedTemplate}
getPreview={getPreview}
/>
)}
</Box>
<Box sx={{ maxWidth: 420 }}>
<DocumentDesignTemplates
templates={invoiceTemplates}
selectTemplate={(template) => setSelectedTemplate(template)}
selectedTemplateId={selectedTemplate?.id}
getPreview={getPreview}
/>
</Box>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useEffect, useState } from 'react';

import { components } from '@/api';
import { ImageWithSkeleton } from '@/ui/imageWithSkeleton';
import { FileViewer } from '@/ui/FileViewer';
import { classNames } from '@/utils/css-utils';
import { t } from '@lingui/macro';
import { useLingui } from '@lingui/react';
Expand All @@ -17,11 +19,11 @@ const StyledLabel = styled('label')({
border: '2px solid transparent',
},
[`&:hover .${templateThumbnailClassName}`]: {
borderColor: 'rgba(86, 43, 214, 1)',
borderColor: '#562BD6',
},
[`& .${selectedtemplateThumbnailClassName}`]: {
outline: '8px solid rgba(238, 235, 255, 1)',
borderColor: 'rgba(86, 43, 214, 1)',
outline: '8px solid #EEEBFF',
borderColor: '#562BD6',
},
});

Expand All @@ -31,17 +33,28 @@ export interface DocumentDesignTemplateProps {
template: DocumentTemplate;
onSelect: () => void;
isSelected: boolean;
getPreview: (id: string) => Promise<string>;
}

export const DocumentDesignTemplate = ({
template,
onSelect,
isSelected,
getPreview,
}: DocumentDesignTemplateProps) => {
const { i18n } = useLingui();
const [preview, setPreview] = useState<string>();

useEffect(() => {
const waitForPreview = async () => {
setPreview(await getPreview(template.id));
};

waitForPreview();
}, [template]);

Check failure on line 54 in packages/sdk-react/src/components/documentDesign/components/DocumentDesignTemplate/DocumentDesignTemplate.tsx

View workflow job for this annotation

GitHub Actions / Linting

React Hook useEffect has a missing dependency: 'getPreview'. Either include it or remove the dependency array. If 'getPreview' changes too often, find the parent component that defines it and wrap that definition in useCallback

return (
<StyledLabel>
<StyledLabel data-testId={`documentTemplate-${template.name}`}>
<Box
className={classNames(
templateThumbnailClassName,
Expand All @@ -67,13 +80,19 @@ export const DocumentDesignTemplate = ({
}}
/>
)}
<ImageWithSkeleton url={template.preview?.url} alt={template.name} />
{!preview && (
<Skeleton
variant="rectangular"
sx={{ width: '100%', height: '100%' }}
/>
)}
{preview && <FileViewer mimetype="application/pdf" url={preview} />}
</Box>
<Typography
variant="body2"
sx={{
marginTop: 2,
color: isSelected ? 'rgba(63, 29, 165, 1)' : 'inherit',
color: isSelected ? '#3F1DA5' : 'inherit',
}}
>
{template.name}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
import { useEffect, useState } from 'react';

import { components } from '@/api';
import { ImageWithSkeleton } from '@/ui/imageWithSkeleton';
import { Box } from '@mui/material';
import { FileViewer } from '@/ui/FileViewer';
import { Box, Skeleton } from '@mui/material';

export interface DocumentDesignTemplatePreviewProps {
template: components['schemas']['TemplateReceivableResponse'];
getPreview: (id: string) => Promise<string>;
}

export const DocumentDesignTemplatePreview = ({
template,
}: DocumentDesignTemplatePreviewProps) => (
<Box sx={{ width: 595 }}>
<ImageWithSkeleton url={template.preview?.url} alt={template.name} />
</Box>
);
getPreview,
}: DocumentDesignTemplatePreviewProps) => {
const [preview, setPreview] = useState<string>();

useEffect(() => {
const waitForPreview = async () => {
setPreview(await getPreview(template.id));
};

waitForPreview();
}, [template]);

Check failure on line 24 in packages/sdk-react/src/components/documentDesign/components/DocumentDesignTemplatePreview/DocumentDesignTemplatePreview.tsx

View workflow job for this annotation

GitHub Actions / Linting

React Hook useEffect has a missing dependency: 'getPreview'. Either include it or remove the dependency array. If 'getPreview' changes too often, find the parent component that defines it and wrap that definition in useCallback

return (
<Box sx={{ width: 595, minHeight: 700 }}>
{!preview && (
<Skeleton
variant="rectangular"
sx={{ width: '100%', height: '100%' }}
/>
)}
{preview && <FileViewer mimetype="application/pdf" url={preview} />}
</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ export interface DocumentDesignTemplatesProps {
templates: DocumentTemplate[];
selectTemplate: (template: DocumentTemplate) => void;
selectedTemplateId?: DocumentTemplate['id'];
getPreview: (id: string) => Promise<string>;
}

export const DocumentDesignTemplates = ({
templates,
selectTemplate,
selectedTemplateId,
getPreview,
}: DocumentDesignTemplatesProps) => {
const { i18n } = useLingui();

Expand All @@ -33,6 +35,7 @@ export const DocumentDesignTemplates = ({
template={template}
onSelect={() => selectTemplate(template)}
isSelected={template.id === selectedTemplateId}
getPreview={getPreview}
/>
</Grid>
))}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 0 additions & 19 deletions packages/sdk-react/src/components/documentDesign/preview.svg

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { components } from '@/api';
import { Services } from '@/api/services';
import { useMoniteContext, FetchToken } from '@/core/context/MoniteContext';

type FetchPdfPreviewParams = {
apiUrl: string;
api: Services;
entityId: string;
version: string;
id: string;
token: string;
};

const getTokenCaller = (fetchToken: FetchToken): (() => Promise<string>) => {
let expiresAt: number;
let token: string;
let promise: null | Promise<{ access_token: string; expires_in: number }>;

return async () => {
const now = Date.now();

if (!promise && (!expiresAt || expiresAt < now)) {
promise = fetchToken();
}

if (promise) {
const { access_token, expires_in } = await promise;

expiresAt = now + expires_in;
token = access_token;

promise = null;
}

return token;
};
};

const pdfPreveiwStore: Record<string, string> = {};

const getPdfPreview = async ({
apiUrl,
api,
entityId,
version,
id,
token,
}: FetchPdfPreviewParams): Promise<string> => {
if (!pdfPreveiwStore[id]) {
const path =
api.documentTemplates.getDocumentTemplatesIdPreview.schema.url.replace(
/{(.*?)}/g,
id
);
const response = await fetch(`${apiUrl}${path}`, {
headers: {
Authorization: `Bearer ${token}`,

Check warning on line 57 in packages/sdk-react/src/components/documentDesign/useDocumentTemplatePreviewLoader.ts

View workflow job for this annotation

GitHub Actions / Linting

disallow literal string
'x-monite-entity-id': entityId,
'x-monite-version': version,
},
});

const blob = await response.blob();
pdfPreveiwStore[id] = URL.createObjectURL(blob);
}

return pdfPreveiwStore[id];
};

export const useDocumentTemplatePreviewLoader = () => {
const { api, fetchToken, apiUrl, entityId, version } = useMoniteContext();
const tokenCaller = getTokenCaller(fetchToken);

const getPreview = async (
id: components['schemas']['TemplateReceivableResponse']['id']
) => {
const token = await tokenCaller();
const preview = await getPdfPreview({
api,
apiUrl,
entityId,
version,
id,
token,
});

return preview;
};

return {
getPreview,
};
};
Loading

0 comments on commit 990b4da

Please sign in to comment.